diff --git a/.github/scripts/screenshots.ps1 b/.github/scripts/screenshots.ps1
index 5b73bc0..4ab5ccb 100644
--- a/.github/scripts/screenshots.ps1
+++ b/.github/scripts/screenshots.ps1
@@ -50,6 +50,15 @@ $adbMode = Join-Path $appDir 'fake-adb.mode'
$fastbootMode = Join-Path $appDir 'fake-fastboot.mode'
$saveRoot = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'Fastboot Studio'
Remove-Item $crashLog, $fakeLog, $fakeAdbLog, $adbMode, $fastbootMode -ErrorAction SilentlyContinue
+
+# What the USB check reads instead of Windows' device list: a phone without a driver and one
+# in MediaTek preloader mode (see UsbScan.cs).
+$fakeUsb = Join-Path $appDir 'fake-usb.txt'
+Set-Content $fakeUsb @(
+ 'Android|USB\VID_0E8D&PID_201C\0123456789ABCDEF|28|',
+ 'MediaTek PreLoader USB VCOM (Android) (COM5)|USB\VID_0E8D&PID_2000\5&2B1E&0&1|0|Ports'
+)
+$env:FASTBOOT_STUDIO_FAKE_USB = $fakeUsb
Remove-Item $saveRoot -Recurse -Force -ErrorAction SilentlyContinue
function Save-Desktop([string] $name) {
@@ -184,6 +193,41 @@ function Go([string] $tabId) {
return $tab
}
+# ---------------------------------------------------------------- helpers
+function Press([string] $id) {
+ $button = Wait-For { By-Id $root $id } 10 $id
+ Wait-For { $button.Current.IsEnabled } 30 "$id to be enabled" | Out-Null
+ $button.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
+}
+
+function Rows($list) {
+ return @($list.FindAll($Scope::Descendants, $rowCond))
+}
+
+# Picks a file in the open file dialog, then waits for that dialog (not whatever window
+# comes next) to go away.
+function Choose-File([string] $path, [string] $what) {
+ $dialog = Wait-For { Find-Dialog } 20 $what
+ $fileHwnd = $dialog.Current.NativeWindowHandle
+ Start-Sleep -Milliseconds 800
+ [System.Windows.Forms.SendKeys]::SendWait($path + '{ENTER}')
+ Wait-For { $d = Find-Dialog; $null -eq $d -or $d.Current.NativeWindowHandle -ne $fileHwnd } 20 "$what to close" | Out-Null
+}
+
+# Presses a dialog button by its label, for questions where the first button is not the answer.
+function Answer-Dialog($dialog, [string] $label) {
+ $cond = New-Object System.Windows.Automation.PropertyCondition($UIA::ControlTypeProperty, $Type::Button)
+ $button = @($dialog.FindAll($Scope::Descendants, $cond)) | Where-Object { $_.Current.Name -eq $label } | Select-Object -First 1
+ if ($null -eq $button) { Fail "the dialog has no '$label' button" }
+ $button.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
+ Wait-For { $null -eq (Find-Dialog) } 10 "the dialog to close" | Out-Null
+ Start-Sleep -Milliseconds 800
+}
+
+function Fastboot-Log([string] $pattern) {
+ return @(Get-Content $fakeLog | Where-Object { $_ -match $pattern }).Count
+}
+
# ---------------------------------------------------------------- payload inspector
$payloadTab = Go 'payload_tab'
Wait-For { Shown $root 'payload_info' } 60 "the payload to open" | Out-Null
@@ -202,6 +246,17 @@ $rowCond = New-Object System.Windows.Automation.OrCondition(
$row = Wait-For { $devices.FindFirst($Scope::Descendants, $rowCond) } 20 "a device to appear"
Save-Window ('{0:D2}-device-list' -f $i); $i++
+# The USB check explains what Windows sees: here a phone without a driver and one in
+# MediaTek preloader mode. It offers Device Manager; answer No.
+Press 'fastboot_usb_check'
+$usb = Wait-For { Find-Dialog } 20 "the USB check"
+Start-Sleep -Milliseconds 600
+Save-Window ('{0:D2}-usb-check' -f $i); $i++
+$usbText = ($usb.FindAll($Scope::Descendants, [System.Windows.Automation.Condition]::TrueCondition) |
+ ForEach-Object { $_.Current.Name } | Where-Object { $_ }) -join ' | '
+Answer-Dialog $usb 'No'
+Write-Host "usb check: $usbText"
+
Select-Item $row
$row.SetFocus()
Send '{ENTER}'
@@ -236,11 +291,18 @@ if ($ExpectedFlashes -gt 0) {
$dialog = Wait-For { Find-Dialog } 20 "the file dialog"
Write-Host "file dialog: '$($dialog.Current.Name)'"
+ $fileHwnd = $dialog.Current.NativeWindowHandle
Start-Sleep -Milliseconds 800
[System.Windows.Forms.SendKeys]::SendWait($Payload + '{ENTER}')
# The file dialog takes a moment to go away; it must not be mistaken for the result.
- Wait-For { $null -eq (Find-Dialog) } 20 "the file dialog to close" | Out-Null
+ Wait-For { $d = Find-Dialog; $null -eq $d -or $d.Current.NativeWindowHandle -ne $fileHwnd } 20 "the file dialog to close" | Out-Null
+
+ # The stand-in phone has no odm: the app offers to create it in super first.
+ $create = Wait-For { Find-Dialog } 30 "the missing partition question"
+ Start-Sleep -Milliseconds 600
+ Save-Window ('{0:D2}-create-missing' -f $i); $i++
+ Answer-Dialog $create 'Yes'
# The app shows "operation completed" (or an error) when it is done. With the file dialog
# gone, the next window to appear is that box. A message box does not expose its text to
@@ -256,43 +318,20 @@ if ($ExpectedFlashes -gt 0) {
$flashes = @(Get-Content $fakeLog | Where-Object { $_ -match ' flash ' }).Count
Write-Host "fastboot flash commands issued: $flashes (expected $ExpectedFlashes)"
if ($flashes -ne $ExpectedFlashes) { Fail "expected $ExpectedFlashes flash commands, saw $flashes" }
+ # odm_a is created (empty) before anything is flashed, and odm is then written to it.
+ $lines = @(Get-Content $fakeLog)
+ $created = -1; $firstFlash = -1
+ for ($n = 0; $n -lt $lines.Count; $n++) {
+ if ($created -lt 0 -and $lines[$n] -match 'create-logical-partition odm_a 0') { $created = $n }
+ if ($firstFlash -lt 0 -and $lines[$n] -match ' flash ') { $firstFlash = $n }
+ }
+ if ($created -lt 0) { Fail "odm_a was not created before flashing the OTA" }
+ if ($created -gt $firstFlash) { Fail "odm_a was created after flashing had started" }
+ if ((Fastboot-Log ' flash odm ') -ne 1) { Fail "odm was not flashed after it was created" }
+ Write-Host "missing partition: odm_a created, then written"
}
# ---------------------------------------------------------------- backup over adb
-function Press([string] $id) {
- $button = Wait-For { By-Id $root $id } 10 $id
- Wait-For { $button.Current.IsEnabled } 30 "$id to be enabled" | Out-Null
- $button.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
-}
-
-function Rows($list) {
- return @($list.FindAll($Scope::Descendants, $rowCond))
-}
-
-# Picks a file in the open file dialog, then waits for that dialog (not whatever window
-# comes next) to go away.
-function Choose-File([string] $path, [string] $what) {
- $dialog = Wait-For { Find-Dialog } 20 $what
- $fileHwnd = $dialog.Current.NativeWindowHandle
- Start-Sleep -Milliseconds 800
- [System.Windows.Forms.SendKeys]::SendWait($path + '{ENTER}')
- Wait-For { $d = Find-Dialog; $null -eq $d -or $d.Current.NativeWindowHandle -ne $fileHwnd } 20 "$what to close" | Out-Null
-}
-
-# Presses a dialog button by its label, for questions where the first button is not the answer.
-function Answer-Dialog($dialog, [string] $label) {
- $cond = New-Object System.Windows.Automation.PropertyCondition($UIA::ControlTypeProperty, $Type::Button)
- $button = @($dialog.FindAll($Scope::Descendants, $cond)) | Where-Object { $_.Current.Name -eq $label } | Select-Object -First 1
- if ($null -eq $button) { Fail "the dialog has no '$label' button" }
- $button.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
- Wait-For { $null -eq (Find-Dialog) } 10 "the dialog to close" | Out-Null
- Start-Sleep -Milliseconds 800
-}
-
-function Fastboot-Log([string] $pattern) {
- return @(Get-Content $fakeLog | Where-Object { $_ -match $pattern }).Count
-}
-
# Hashes every image a backup recorded in SHA256SUMS against the file it wrote.
function Check-Backup([string] $what) {
$folder = Get-ChildItem (Join-Path $saveRoot 'Backups') -Directory | Sort-Object LastWriteTime | Select-Object -Last 1
@@ -391,6 +430,18 @@ $vbDone = Wait-For { Find-Dialog } 30 "vbmeta to be flashed"
Close-Dialog $vbDone
if ((Fastboot-Log 'flash --disable-verity --disable-verification vbmeta_a ') -ne 1) { Fail "vbmeta was not flashed with --disable-verity --disable-verification" }
Write-Host "vbmeta: flashed with verification disabled"
+# Super: the card on the partition tab, and a 12 GiB image (a sparse file of a few bytes)
+# that cannot fit; the app says by how much and what could make room. Answer No.
+Filter-Partitions 'product_a'
+Save-Window ('{0:D2}-super-space' -f $i); $i++
+Press 'fastboot_flash'
+Choose-File (Join-Path (Split-Path $Payload) 'big-sparse.img') "the image dialog"
+$full = Wait-For { Find-Dialog } 20 "the super space warning"
+Start-Sleep -Milliseconds 600
+Save-Window ('{0:D2}-super-full' -f $i); $i++
+Answer-Dialog $full 'No'
+if ((Fastboot-Log 'flash product_a .*big-sparse') -gt 0) { Fail "an image too big for super was flashed" }
+Write-Host "super: a 12 GiB image was stopped before flashing"
(By-Id $root 'fastboot_partition_name_textbox').GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern).SetValue('')
# Boot once: init_boot has no kernel and is refused before anything is sent.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 268bb44..3ef3f8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@
+## v2.2.0
+- بطاقة مساحة super: كم ماخذ كل سلوت وكم الفاضي
+- تحذير قبل التفليش إذا الصور ما تدخل في super، مع طرق توفير المساحة
+- إنشاء الأقسام الناقصة تلقائياً عند تفليش التحديث
+- زر «فحص USB»: يكشف التعريف الناقص ووضع EDL و BROM
+
## v2.1.0
- حماية تبديل الخانة: تحذير أو منع إذا الخانة الثانية فاضية أو ما تقلع
- الأقسام الحساسة (xbl و abl و modem و persist و preloader…) تطلب كتابة اسم القسم
@@ -108,6 +114,19 @@
---
+## Version 2.2 in short
+- **Super space** card on the Device page (fastbootd): what slot a, slot b and update copies
+ (COW) take in super, and about how much is free.
+- **Room check before flashing** in fastbootd: when the images are known not to fit in super,
+ the app says by how much and what could make room (the other slot's partitions, COW
+ partitions or a pending update) before anything is written.
+- **Missing partitions are created**: when an OTA writes logical partitions the phone does not
+ have, they are created in super (empty) and then flashed. The note explains fastbootd's
+ `default` group. The manual Create window carries the same note.
+- **USB check** on the device list: reads what Windows sees on USB and tells a missing or broken
+ driver (with the steps to fix it), Qualcomm EDL (9008), MediaTek BROM/preloader, Unisoc and
+ Samsung download modes, and a phone in Android (offering to reboot it to the bootloader).
+
## Version 2.1 in short
- **Slot switch protection**: reads `slot-successful`, `slot-unbootable` and `slot-retry-count`.
A slot that has not booted yet warns; an unbootable slot or one with no retries left asks in
diff --git a/FastbootActionWindow.xaml b/FastbootActionWindow.xaml
index c82f937..3a12ba0 100644
--- a/FastbootActionWindow.xaml
+++ b/FastbootActionWindow.xaml
@@ -14,6 +14,7 @@
+
diff --git a/FastbootActionWindow.xaml.cs b/FastbootActionWindow.xaml.cs
index 3689473..b089268 100644
--- a/FastbootActionWindow.xaml.cs
+++ b/FastbootActionWindow.xaml.cs
@@ -72,6 +72,9 @@ public FastbootActionWindow(StartType type,
{
case StartType.CREATE:
this.Title = Properties.Resources.fastboot_create_dynamic_partition;
+ // fastbootd adds new partitions to the "default" group, not the phone's own.
+ this.note.Text = Properties.Resources.create_default_group_note;
+ this.note.Visibility = Visibility.Visible;
break;
case StartType.RESIZE:
this.Title = Properties.Resources.fastboot_expand_dynamic_partition;
diff --git a/FastbootEnhance.csproj b/FastbootEnhance.csproj
index 792c655..752f2cb 100644
--- a/FastbootEnhance.csproj
+++ b/FastbootEnhance.csproj
@@ -42,6 +42,11 @@
+
+
+
+
+
True
diff --git a/FastbootUI.cs b/FastbootUI.cs
index 7c4c8b4..8a258ee 100644
--- a/FastbootUI.cs
+++ b/FastbootUI.cs
@@ -2,6 +2,7 @@
using FastbootEnhance.Core.Fastboot;
using FastbootEnhance.Core.Images;
using FastbootEnhance.Core.Payload;
+using FastbootEnhance.Core.Usb;
using System;
using System.Collections.Generic;
using System.IO;
@@ -395,6 +396,7 @@ static void showFastbootVars()
MainWindow.THIS.fastboot_mode_badge.Visibility = Visibility.Visible;
MainWindow.THIS.fastboot_checks.ItemsSource = buildChecks();
+ showSuperSpace();
}
/// What to look at before writing anything to this device.
@@ -428,6 +430,211 @@ static List buildChecks()
return checks;
}
+ /// The "Super" card on the partition tab: what each slot takes and what is free.
+ static void showSuperSpace()
+ {
+ SuperSpace space = SuperSpace.From(fastbootData);
+ MainWindow.THIS.fastboot_super_bar.Value = 0;
+ MainWindow.THIS.fastboot_super_bar.Foreground = Palette.Get("Accent");
+
+ if (!fastbootData.fastbootd)
+ {
+ MainWindow.THIS.fastboot_super_text.Text = Properties.Resources.super_space_bootloader;
+ return;
+ }
+ if (!space.Known)
+ {
+ MainWindow.THIS.fastboot_super_text.Text = Properties.Resources.super_space_unknown;
+ return;
+ }
+
+ MainWindow.THIS.fastboot_super_bar.Value = Math.Min(100, space.Used * 100.0 / space.SuperSize);
+ MainWindow.THIS.fastboot_super_bar.Foreground = Palette.Get(
+ space.Free < (512L << 20) ? "Danger" : space.Free < (2L << 30) ? "Warn" : "Accent");
+
+ List lines = new List();
+ lines.Add(string.Format(Properties.Resources.super_space_total,
+ Helper.byte2AUnit(space.SuperSize), Helper.byte2AUnit(space.Free)));
+ if (space.CurrentSlot != null)
+ lines.Add(string.Format(Properties.Resources.super_space_slot, space.CurrentSlot,
+ Helper.byte2AUnit(space.CurrentSlotBytes)) + " " + Properties.Resources.super_space_in_use);
+ if (space.OtherSlot != null)
+ lines.Add(string.Format(Properties.Resources.super_space_slot, space.OtherSlot,
+ Helper.byte2AUnit(space.OtherSlotBytes)));
+ if (space.CowBytes > 0)
+ lines.Add(string.Format(Properties.Resources.super_space_cow, Helper.byte2AUnit(space.CowBytes)));
+ if (space.UnslottedBytes > 0)
+ lines.Add(string.Format(Properties.Resources.super_space_unslotted, Helper.byte2AUnit(space.UnslottedBytes)));
+ MainWindow.THIS.fastboot_super_text.Text = string.Join("\n", lines);
+ }
+
+ ///
+ /// Before writing to logical partitions in fastbootd: when super is known to be too
+ /// small for the images, says by how much and what could make room, and asks.
+ /// True to go ahead.
+ ///
+ static bool superSpaceAllowed(IEnumerable> writes, IEnumerable created = null)
+ {
+ if (fastbootData == null || !fastbootData.fastbootd)
+ return true;
+
+ SuperSpace space = SuperSpace.From(fastbootData);
+ SuperFit fit = space.Check(writes, created);
+ appendLog("super space: " + (space.Known ? Helper.byte2AUnit(space.Free) + " free" : "size unknown")
+ + ", writes need " + Helper.byte2AUnit(fit.Needed) + (fit.Fits ? "" : " (does not fit)"));
+ if (fit.Fits)
+ return true;
+
+ List ways = new List();
+ if (space.OtherSlotPartitions.Count > 0)
+ ways.Add(string.Format(Properties.Resources.super_full_other_slot, space.OtherSlot,
+ string.Join(", ", space.OtherSlotPartitions.Take(6)
+ .Select(p => p.Name + " (" + Helper.byte2AUnit(p.Size) + ")"))));
+ if (space.CowPartitions.Count > 0 || fastbootData.HasPendingUpdate)
+ ways.Add(Properties.Resources.super_full_cow);
+ if (ways.Count == 0)
+ ways.Add(Properties.Resources.super_full_nothing);
+
+ string text = string.Format(Properties.Resources.super_full,
+ Helper.byte2AUnit(fit.Needed), string.Join(", ", fit.Growing), Helper.byte2AUnit(fit.Free))
+ + "\n\n" + string.Join("\n\n", ways.Select(w => "• " + w))
+ + "\n\n" + Properties.Resources.super_full_ask;
+ return Helper.confirm(text, Properties.Resources.super_full_title, true);
+ }
+
+ ///
+ /// The USB check: looks at what Windows sees on USB and explains why a phone may be
+ /// missing from the list (no driver, a broken driver, a download mode, Android mode).
+ ///
+ static void checkUsb()
+ {
+ MainWindow.THIS.fastboot_usb_check.IsEnabled = false;
+ IReadOnlyList findings = null;
+ Exception failure = null;
+
+ Helper.offloadAndRun(new Action(delegate
+ {
+ try
+ {
+ findings = UsbDiagnosis.Diagnose(UsbScan.Read());
+ }
+ catch (Exception e)
+ {
+ failure = e;
+ }
+ }), new Action(delegate
+ {
+ MainWindow.THIS.fastboot_usb_check.IsEnabled = true;
+ if (failure != null)
+ {
+ appendLog("usb check failed: " + failure.Message);
+ ThemedDialog.Show(failure.Message, Properties.Resources.error, MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ showUsbFindings(findings);
+ }));
+ }
+
+ static void showUsbFindings(IReadOnlyList findings)
+ {
+ List lines = new List();
+ bool driverTrouble = false;
+ bool adbReady = false;
+
+ foreach (UsbFinding finding in findings)
+ {
+ appendLog("usb: " + finding.Kind + ", " + finding.Device.Name + " " + finding.UsbId
+ + ", code " + finding.Device.ErrorCode);
+ string device = finding.Device.Name + (finding.UsbId != null ? " (" + finding.UsbId + ")" : "");
+ switch (finding.Kind)
+ {
+ case UsbFindingKind.MissingDriver:
+ lines.Add(string.Format(Properties.Resources.usb_missing_driver, device));
+ driverTrouble = true;
+ break;
+ case UsbFindingKind.DriverProblem:
+ lines.Add(string.Format(Properties.Resources.usb_driver_problem, device, finding.Device.ErrorCode));
+ driverTrouble = true;
+ break;
+ case UsbFindingKind.QualcommEdl:
+ lines.Add(string.Format(Properties.Resources.usb_edl, device));
+ break;
+ case UsbFindingKind.MediaTekDownload:
+ lines.Add(string.Format(Properties.Resources.usb_mediatek, device));
+ break;
+ case UsbFindingKind.UnisocDownload:
+ lines.Add(string.Format(Properties.Resources.usb_unisoc, device));
+ break;
+ case UsbFindingKind.SamsungDownload:
+ lines.Add(string.Format(Properties.Resources.usb_samsung, device));
+ break;
+ case UsbFindingKind.FastbootReady:
+ lines.Add(string.Format(Properties.Resources.usb_fastboot_ready, device));
+ break;
+ case UsbFindingKind.AdbReady:
+ lines.Add(string.Format(Properties.Resources.usb_adb_ready, device));
+ adbReady = true;
+ break;
+ }
+ }
+
+ if (lines.Count == 0)
+ {
+ ThemedDialog.Show(Properties.Resources.usb_nothing, Properties.Resources.usb_check_title,
+ MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ string text = string.Join("\n\n", lines.Select(line => "• " + line));
+
+ if (driverTrouble)
+ {
+ if (ThemedDialog.Show(text + "\n\n" + Properties.Resources.usb_driver_howto
+ + "\n\n" + Properties.Resources.usb_open_device_manager,
+ Properties.Resources.usb_check_title, MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
+ {
+ try
+ {
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("devmgmt.msc") { UseShellExecute = true });
+ }
+ catch (Exception e)
+ {
+ ThemedDialog.Show(e.Message, Properties.Resources.error, MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+ return;
+ }
+
+ if (adbReady)
+ {
+ if (!Helper.confirm(text + "\n\n" + Properties.Resources.usb_reboot_bootloader_ask, Properties.Resources.usb_check_title))
+ return;
+
+ string problem = null;
+ Helper.offloadAndRun(new Action(delegate
+ {
+ try
+ {
+ Adb.Result result = Adb.Run("reboot bootloader", Adb.ShortCommand);
+ if (!result.Succeeded)
+ problem = result.Problem;
+ }
+ catch (Exception e)
+ {
+ problem = e.Message;
+ }
+ }), new Action(delegate
+ {
+ appendLog("adb reboot bootloader: " + (problem ?? "ok"));
+ if (problem != null)
+ ThemedDialog.Show(problem, Properties.Resources.error, MessageBoxButton.OK, MessageBoxImage.Error);
+ }));
+ return;
+ }
+
+ ThemedDialog.Show(text, Properties.Resources.usb_check_title, MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+
static void change_page()
{
bool showActions = cur_status == FastbootStatus.show_actions;
@@ -752,8 +959,8 @@ static void openPayloadThenFlash(string path, string serial)
return;
}
- string unknown = findUnknownPartitions(opened);
- if (unknown != null)
+ MissingPartitions missing = findMissingPartitions(opened);
+ if (missing.Unknown.Count > 0)
{
opened.Dispose();
action_unlock();
@@ -761,10 +968,23 @@ static void openPayloadThenFlash(string path, string serial)
? "\n" + Properties.Resources.fastboot_unknown_partition_str1
: "\n" + Properties.Resources.fastboot_unknown_partition_str2;
ThemedDialog.Show(Properties.Resources.fastboot_unknown_partition_str0
- + "\n" + unknown + hint);
+ + "\n" + string.Join(" ", missing.Unknown) + hint);
return;
}
+ // Logical partitions the update adds (or the phone lost) are created first.
+ if (missing.Create.Count > 0)
+ {
+ appendLog("missing logical partitions: " + string.Join(", ", missing.Create));
+ if (!Helper.confirm(string.Format(Properties.Resources.create_missing,
+ string.Join(", ", missing.Create)), Properties.Resources.create_missing_title))
+ {
+ opened.Dispose();
+ action_unlock();
+ return;
+ }
+ }
+
List blocked = opened.Partitions
.Where(part => !part.CanExtract)
.Select(part => part.Name)
@@ -802,36 +1022,68 @@ static void openPayloadThenFlash(string path, string serial)
return;
}
- flashPayload(opened, serial);
+ // In fastbootd each logical partition is resized to its image as it is
+ // written; make sure super can take them, in the order they are written.
+ Dictionary createdAs = new Dictionary(StringComparer.Ordinal);
+ for (int i = 0; i < missing.Create.Count; i++)
+ createdAs[missing.CreateFrom[i]] = missing.Create[i];
+ List> writes = opened.Partitions
+ .Select(part => new KeyValuePair(
+ createdAs.ContainsKey(part.Name) ? createdAs[part.Name] : part.Name, part.UnpackedSize))
+ .ToList();
+ if (!superSpaceAllowed(writes, missing.Create))
+ {
+ opened.Dispose();
+ action_unlock();
+ return;
+ }
+
+ flashPayload(opened, serial, missing.Create);
}));
}
///
- /// Returns a space separated list of payload partitions the device does not have,
- /// or null when every partition can be written.
+ /// The partitions of the payload the phone does not have, split into those that can
+ /// be created in super and those that cannot. Nothing when the user chose to ignore them.
///
- static string findUnknownPartitions(PayloadFile payload)
+ static MissingPartitions findMissingPartitions(PayloadFile payload)
{
if (MainWindow.THIS.ignore_unknown_part.IsChecked == true)
- return null;
+ return MissingPartitions.Find(fastbootData, new string[0], new string[0]);
- List missing = new List();
+ return MissingPartitions.Find(fastbootData, payload.Partitions.Select(part => part.Name),
+ payload.LogicalPartitionNames());
+ }
- foreach (PayloadPartitionInfo part in payload.Partitions)
+ ///
+ /// "create-logical-partition NAME 0" for each partition; fastbootd then sizes each one to
+ /// the image written to it. Returns the error, or null when all were created.
+ ///
+ static string createLogicalPartitions(string serial, IReadOnlyList names, CancellationToken cancel)
+ {
+ foreach (string name in names)
{
- long size;
- if (fastbootData.partition_size.TryGetValue(part.Name, out size))
- continue;
-
- if (fastbootData.current_slot != null &&
- fastbootData.partition_size.TryGetValue(
- part.Name + "_" + fastbootData.current_slot, out size))
- continue;
-
- missing.Add(part.Name);
+ cancel.ThrowIfCancellationRequested();
+ appendLog("Creating logical partition " + name);
+ int? exitCode;
+ using (Fastboot fastboot = new Fastboot(serial,
+ "create-logical-partition \"" + name + "\" 0", Fastboot.LongCommand))
+ {
+ while (true)
+ {
+ string line = fastboot.stderr.ReadLine();
+ if (line == null)
+ break;
+ appendLog(line);
+ }
+ exitCode = fastboot.WaitForExit();
+ }
+ if (exitCode != 0)
+ return name + ": create-logical-partition " + (exitCode == null
+ ? "did not finish in time"
+ : "exited with code " + exitCode);
}
-
- return missing.Count == 0 ? null : string.Join(" ", missing);
+ return null;
}
///
@@ -840,7 +1092,7 @@ static string findUnknownPartitions(PayloadFile payload)
/// payload that turns out to be damaged cannot leave the phone half written. Each image
/// is deleted as soon as it is flashed to keep the staging directory small.
///
- static void flashPayload(PayloadFile payload, string serial)
+ static void flashPayload(PayloadFile payload, string serial, IReadOnlyList create)
{
if (serial == null)
throw new ArgumentNullException(nameof(serial));
@@ -985,7 +1237,16 @@ static void flashPayload(PayloadFile payload, string serial)
long flashedBytes = 0;
List results = report.Results.ToList();
- for (int i = 0; i < results.Count; i++)
+ // Only now, with every image extracted and verified, is super touched.
+ error = createLogicalPartitions(serial, create, cancel.Token);
+ if (error != null)
+ {
+ appendLog(error);
+ foreach (PartitionResult result in results)
+ updateRow(result.Partition, FlashRow.Stage.NotWritten, null);
+ }
+
+ for (int i = 0; error == null && i < results.Count; i++)
{
PartitionResult result = results[i];
appendLog("Flashing " + result.Partition);
@@ -1161,6 +1422,11 @@ public static void init()
refresher.Start();
MainWindow.THIS.main_tabs.Tag = string.Format(Properties.Resources.rail_devices, 0);
+ MainWindow.THIS.fastboot_usb_check.Click += delegate
+ {
+ checkUsb();
+ };
+
MainWindow.THIS.fastboot_devices_list.MouseDoubleClick += delegate
{
openSelectedDevice();
@@ -1269,6 +1535,24 @@ public static void init()
Helper.fileSelect(new Helper.PathSelectCallback(delegate (string path)
{
+ // fastbootd resizes a logical partition to its image: check super has room.
+ bool? isLogical;
+ if (fastbootData.partition_is_logical.TryGetValue(target, out isLogical) && isLogical == true)
+ {
+ long imageSize;
+ try
+ {
+ imageSize = SuperSpace.WrittenSize(path);
+ }
+ catch (Exception e)
+ {
+ ThemedDialog.Show(e.Message, Properties.Resources.error, MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ if (!superSpaceAllowed(new[] { new KeyValuePair(target, imageSize) }))
+ return;
+ }
+
string ext_arg = "";
if (target == "vbmeta" || target == "vbmeta_" + fastbootData.current_slot
diff --git a/MainWindow.xaml b/MainWindow.xaml
index cecd642..3b5afad 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -54,10 +54,15 @@
-
-
-
-
+
+
+
+
+
+
+
+
@@ -181,7 +186,8 @@
-
+
+
@@ -191,6 +197,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
index 42a93b6..7ed21f7 100644
--- a/Properties/Resources.Designer.cs
+++ b/Properties/Resources.Designer.cs
@@ -4044,5 +4044,284 @@ public static string boot_once_started {
return ResourceManager.GetString("boot_once_started", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Super.
+ ///
+ public static string super_space_title {
+ get {
+ return ResourceManager.GetString("super_space_title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Shown in fastbootd: the bootloader does not list the partitions inside super..
+ ///
+ public static string super_space_bootloader {
+ get {
+ return ResourceManager.GetString("super_space_bootloader", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The phone does not report the size of super..
+ ///
+ public static string super_space_unknown {
+ get {
+ return ResourceManager.GetString("super_space_unknown", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} in all, about {1} free.
+ ///
+ public static string super_space_total {
+ get {
+ return ResourceManager.GetString("super_space_total", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Slot {0}: {1}.
+ ///
+ public static string super_space_slot {
+ get {
+ return ResourceManager.GetString("super_space_slot", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to (in use).
+ ///
+ public static string super_space_in_use {
+ get {
+ return ResourceManager.GetString("super_space_in_use", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Update copies (COW): {0}.
+ ///
+ public static string super_space_cow {
+ get {
+ return ResourceManager.GetString("super_space_cow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Without a slot: {0}.
+ ///
+ public static string super_space_unslotted {
+ get {
+ return ResourceManager.GetString("super_space_unslotted", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Not enough room in super.
+ ///
+ public static string super_full_title {
+ get {
+ return ResourceManager.GetString("super_full_title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to These images need about {0} more room in super ({1}), but only about {2} is free. fastbootd would stop with "Not enough space to resize partition". Ways to make room:.
+ ///
+ public static string super_full {
+ get {
+ return ResourceManager.GetString("super_full", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Delete the partitions of slot {0}: {1}. Slot {0} will not start until it is flashed again..
+ ///
+ public static string super_full_other_slot {
+ get {
+ return ResourceManager.GetString("super_full_other_slot", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Remove what an earlier update left behind: cancel the pending update (Cancel update), or delete the COW partitions..
+ ///
+ public static string super_full_cow {
+ get {
+ return ResourceManager.GetString("super_full_cow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Nothing else is using super; these images are bigger than this phone's super..
+ ///
+ public static string super_full_nothing {
+ get {
+ return ResourceManager.GetString("super_full_nothing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The numbers are estimates. Flash anyway?.
+ ///
+ public static string super_full_ask {
+ get {
+ return ResourceManager.GetString("super_full_ask", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Missing partitions.
+ ///
+ public static string create_missing_title {
+ get {
+ return ResourceManager.GetString("create_missing_title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The phone has no {0}. They are partitions this update places inside super, so they can be created there now (empty) and then written. fastbootd puts new partitions in the "default" group, not in the [rest of string was truncated].
+ ///
+ public static string create_missing {
+ get {
+ return ResourceManager.GetString("create_missing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to fastbootd puts new partitions in the "default" group, not in the phone's own group (such as main_a). It works; the next full update puts it in the usual group..
+ ///
+ public static string create_default_group_note {
+ get {
+ return ResourceManager.GetString("create_default_group_note", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to USB check.
+ ///
+ public static string usb_check {
+ get {
+ return ResourceManager.GetString("usb_check", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to USB check.
+ ///
+ public static string usb_check_title {
+ get {
+ return ResourceManager.GetString("usb_check_title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: Windows has no driver for it, so fastboot and adb cannot see it..
+ ///
+ public static string usb_missing_driver {
+ get {
+ return ResourceManager.GetString("usb_missing_driver", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: its driver is installed but not working (code {1}). Unplug and plug it in again, or reinstall the driver..
+ ///
+ public static string usb_driver_problem {
+ get {
+ return ResourceManager.GetString("usb_driver_problem", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: the phone is in Qualcomm emergency download mode (EDL, 9008), used by QFIL. fastboot cannot reach this mode; hold the power key for 10 to 15 seconds to leave it..
+ ///
+ public static string usb_edl {
+ get {
+ return ResourceManager.GetString("usb_edl", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: the phone is in MediaTek BROM or preloader mode, used by SP Flash Tool. fastboot cannot reach it; unplug it and start fastboot with the phone's key combination..
+ ///
+ public static string usb_mediatek {
+ get {
+ return ResourceManager.GetString("usb_mediatek", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: the phone is in Unisoc (Spreadtrum) download mode. fastboot cannot reach it; unplug it and start fastboot with the phone's key combination..
+ ///
+ public static string usb_unisoc {
+ get {
+ return ResourceManager.GetString("usb_unisoc", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: the phone is in Samsung Download (Odin) mode. Samsung phones have no fastboot; Odin is the tool for this mode..
+ ///
+ public static string usb_samsung {
+ get {
+ return ResourceManager.GetString("usb_samsung", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: a fastboot interface with a working driver. If the phone is not listed, unplug it and plug it in again..
+ ///
+ public static string usb_fastboot_ready {
+ get {
+ return ResourceManager.GetString("usb_fastboot_ready", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: the phone is connected in Android or recovery (adb), not in fastboot..
+ ///
+ public static string usb_adb_ready {
+ get {
+ return ResourceManager.GetString("usb_adb_ready", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Windows sees no phone on USB. • Use a data cable; many cables only charge. • Try another USB port, a USB 2.0 port directly on the PC if you can. • Make sure the phone is in fastboot (bootloader or[rest of string was truncated].
+ ///
+ public static string usb_nothing {
+ get {
+ return ResourceManager.GetString("usb_nothing", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to To install the driver: get the Google USB Driver (developer.android.com/studio/run/win-usb) or the phone maker's driver. If Windows still shows the phone without a driver, in Device Manager right-clic[rest of string was truncated].
+ ///
+ public static string usb_driver_howto {
+ get {
+ return ResourceManager.GetString("usb_driver_howto", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Device Manager now?.
+ ///
+ public static string usb_open_device_manager {
+ get {
+ return ResourceManager.GetString("usb_open_device_manager", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Reboot it to the bootloader now, so it shows up here?.
+ ///
+ public static string usb_reboot_bootloader_ask {
+ get {
+ return ResourceManager.GetString("usb_reboot_bootloader_ask", resourceCulture);
+ }
+ }
}
}
diff --git a/Properties/Resources.ar.resx b/Properties/Resources.ar.resx
index 36336d2..01805c2 100644
--- a/Properties/Resources.ar.resx
+++ b/Properties/Resources.ar.resx
@@ -1493,4 +1493,107 @@
تم إرسال الصورة والجوال يشغّلها الآن. إعادة تشغيل الجوال بشكل عادي ترجّعه للنظام المثبّت.
+
+ مساحة Super
+
+
+ تظهر في وضع fastbootd فقط، لأن البوت لودر ما يعرض الأقسام اللي داخل super.
+
+
+ الجوال ما يعطي حجم super.
+
+
+ الحجم الكلي {0}، والفاضي تقريباً {1}
+
+
+ السلوت {0}: {1}
+
+
+ (الحالي)
+
+
+ نسخ التحديث (COW): {0}
+
+
+ بدون سلوت: {0}
+
+
+ المساحة في super ما تكفي
+
+
+ الصور هذي تحتاج تقريباً {0} زيادة في super ({1})، والفاضي تقريباً {2} بس. fastbootd راح يوقف برسالة "Not enough space to resize partition".
+
+طرق توفير مساحة:
+
+
+ احذف أقسام السلوت {0}: {1}. السلوت {0} ما راح يقلع لين تفلشه من جديد.
+
+
+ احذف بقايا التحديث السابق: ألغِ التحديث المعلّق (زر إلغاء التحديث)، أو احذف أقسام COW.
+
+
+ ما في شي ثاني ماخذ مساحة في super، يعني الصور أكبر من super حق هذا الجوال.
+
+
+ الأرقام تقريبية. تفليش على أي حال؟
+
+
+ أقسام ناقصة
+
+
+ الجوال ما فيه {0}. هذي أقسام يحطها التحديث داخل super، فممكن تنشأ الحين (فاضية) وبعدها تتفلش.
+
+fastbootd يحط الأقسام الجديدة في مجموعة "default"، مو في مجموعة الجوال الأصلية (مثل main_a). الجوال يقلع عادي، وأول تحديث كامل أو تفليش super من المصنع يرجعها لمجموعتها المعتادة.
+
+إنشاؤها والمتابعة؟
+
+
+ fastbootd يحط القسم الجديد في مجموعة "default" مو في مجموعة الجوال الأصلية (مثل main_a). يشتغل عادي، وأول تحديث كامل يرجعه لمجموعته المعتادة.
+
+
+ فحص USB
+
+
+ فحص اتصال USB
+
+
+ {0}: ويندوز ما عنده تعريف له، فـ fastboot و adb ما يشوفونه.
+
+
+ {0}: التعريف مثبت بس ما يشتغل (رمز {1}). افصل الجوال ورجّعه، أو أعد تثبيت التعريف.
+
+
+ {0}: الجوال في وضع EDL حق كوالكوم (9008)، وهو الوضع اللي يستخدمه QFIL. fastboot ما يوصل لهذا الوضع، اضغط زر التشغيل 10 إلى 15 ثانية عشان يطلع منه.
+
+
+ {0}: الجوال في وضع BROM أو Preloader حق ميدياتك، وهو الوضع اللي يستخدمه SP Flash Tool. fastboot ما يوصل له، افصل الجوال وادخل الفاست بوت بأزرار الجوال.
+
+
+ {0}: الجوال في وضع التحميل حق Unisoc (سبريدترم). fastboot ما يوصل له، افصل الجوال وادخل الفاست بوت بأزرار الجوال.
+
+
+ {0}: الجوال في وضع التحميل حق سامسونج (Odin). جوالات سامسونج ما فيها fastboot، والأداة لهذا الوضع هي Odin.
+
+
+ {0}: واجهة فاست بوت وتعريفها شغّال. إذا الجوال مو ظاهر في القائمة، افصله ورجّعه.
+
+
+ {0}: الجوال متصل وهو في أندرويد أو الريكفري (adb)، مو في الفاست بوت.
+
+
+ ويندوز ما يشوف أي جوال على USB.
+
+• استخدم كيبل بيانات، كثير من الكيبلات للشحن بس.
+• جرّب منفذ USB ثاني، ويفضّل منفذ USB 2.0 في الجهاز نفسه.
+• تأكد إن الجوال في وضع الفاست بوت (bootloader أو fastbootd)، أو إن تصحيح USB مفعّل إذا تبي adb.
+
+
+ لتثبيت التعريف: حمّل Google USB Driver من developer.android.com/studio/run/win-usb أو تعريف الشركة المصنعة. إذا ويندوز للحين يعرض الجوال بدون تعريف: في إدارة الأجهزة اضغط عليه بالزر اليمين ← تحديث برنامج التشغيل ← استعراض جهاز الكمبيوتر ← السماح لي بالاختيار ← Android Device ← "Android Bootloader Interface" للفاست بوت، أو "Android ADB Interface" لـ adb.
+
+
+ فتح إدارة الأجهزة الحين؟
+
+
+ إعادة تشغيله للبوت لودر الحين عشان يظهر هنا؟
+
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
index b8956c2..8b43148 100644
--- a/Properties/Resources.resx
+++ b/Properties/Resources.resx
@@ -1493,4 +1493,107 @@ Flash it only with the image made for this exact phone, and keep a backup of the
The image was sent and the phone is starting it. Restarting the phone normally returns to the installed system.
+
+ Super
+
+
+ Shown in fastbootd: the bootloader does not list the partitions inside super.
+
+
+ The phone does not report the size of super.
+
+
+ {0} in all, about {1} free
+
+
+ Slot {0}: {1}
+
+
+ (in use)
+
+
+ Update copies (COW): {0}
+
+
+ Without a slot: {0}
+
+
+ Not enough room in super
+
+
+ These images need about {0} more room in super ({1}), but only about {2} is free. fastbootd would stop with "Not enough space to resize partition".
+
+Ways to make room:
+
+
+ Delete the partitions of slot {0}: {1}. Slot {0} will not start until it is flashed again.
+
+
+ Remove what an earlier update left behind: cancel the pending update (Cancel update), or delete the COW partitions.
+
+
+ Nothing else is using super; these images are bigger than this phone's super.
+
+
+ The numbers are estimates. Flash anyway?
+
+
+ Missing partitions
+
+
+ The phone has no {0}. They are partitions this update places inside super, so they can be created there now (empty) and then written.
+
+fastbootd puts new partitions in the "default" group, not in the phone's own group (such as main_a). The phone starts normally; the next full update, or flashing the factory super, puts them back in the usual group.
+
+Create them and continue?
+
+
+ fastbootd puts new partitions in the "default" group, not in the phone's own group (such as main_a). It works; the next full update puts it in the usual group.
+
+
+ USB check
+
+
+ USB check
+
+
+ {0}: Windows has no driver for it, so fastboot and adb cannot see it.
+
+
+ {0}: its driver is installed but not working (code {1}). Unplug and plug it in again, or reinstall the driver.
+
+
+ {0}: the phone is in Qualcomm emergency download mode (EDL, 9008), used by QFIL. fastboot cannot reach this mode; hold the power key for 10 to 15 seconds to leave it.
+
+
+ {0}: the phone is in MediaTek BROM or preloader mode, used by SP Flash Tool. fastboot cannot reach it; unplug it and start fastboot with the phone's key combination.
+
+
+ {0}: the phone is in Unisoc (Spreadtrum) download mode. fastboot cannot reach it; unplug it and start fastboot with the phone's key combination.
+
+
+ {0}: the phone is in Samsung Download (Odin) mode. Samsung phones have no fastboot; Odin is the tool for this mode.
+
+
+ {0}: a fastboot interface with a working driver. If the phone is not listed, unplug it and plug it in again.
+
+
+ {0}: the phone is connected in Android or recovery (adb), not in fastboot.
+
+
+ Windows sees no phone on USB.
+
+• Use a data cable; many cables only charge.
+• Try another USB port, a USB 2.0 port directly on the PC if you can.
+• Make sure the phone is in fastboot (bootloader or fastbootd) or, for adb, that USB debugging is on.
+
+
+ To install the driver: get the Google USB Driver (developer.android.com/studio/run/win-usb) or the phone maker's driver. If Windows still shows the phone without a driver, in Device Manager right-click it → Update driver → Browse my computer → Let me pick → Android Device → "Android Bootloader Interface" for fastboot, or "Android ADB Interface" for adb.
+
+
+ Open Device Manager now?
+
+
+ Reboot it to the bootloader now, so it shows up here?
+
\ No newline at end of file
diff --git a/README.ar.md b/README.ar.md
index 737fd47..f54929b 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -21,6 +21,11 @@
> 🆕 **نسخة جديدة.** Fastboot Studio هو الإصدار الجديد من Fastboot Enhance: واجهة جديدة بالكامل، وصفحات جديدة، وحماية أكثر للجوال.
>
+> **الجديد في 2.2:**
+> - بطاقة مساحة super، وتأكد إن الصور تدخل قبل التفليش.
+> - إنشاء الأقسام الناقصة اللي يحتاجها التحديث.
+> - زر **فحص USB** يكشف التعريف الناقص ووضع EDL و BROM.
+>
> **الجديد في 2.1:**
> - حماية تبديل الخانة: يحذّر أو يمنع إذا الخانة الثانية فاضية أو ما تقلع.
> - الأقسام الحساسة (xbl و abl و modem و persist و preloader…) ما تتفلش إلا بعد كتابة اسم القسم.
@@ -59,11 +64,16 @@
كل ما يقدر الفاست بوت يعرضه أو يسويه، على الجوال الموصّل.
- **الأجهزة:** يعرض كل جوال في وضع **bootloader** أو **fastbootd**. اضغط عليه مرتين لفتحه.
+- **فحص USB:** إذا الجوال موصّل وما ظهر، الزر يشوف وش يشوف ويندوز ويقول لك السبب:
+ - تعريف ناقص أو خربان، مع خطوات تثبيته.
+ - وضع ما يوصله الفاست بوت: EDL حق كوالكوم (9008)، BROM أو Preloader حق ميدياتك، وضع التحميل حق Unisoc أو سامسونج.
+ - الجوال للحين في أندرويد، ويعرض عليك إعادة تشغيله للبوت لودر.
- **الخصائص الأساسية:** كل قيم `getvar`، مثل:
- الطراز وعدد الخانات.
- حالة قفل البوت لودر والخانة الحالية.
- حالة تحديث Virtual A/B.
- **الأقسام:** كل قسم بحجمه، وهل هو منطقي (داخل super) أم لا، مع بحث بالاسم.
+- **مساحة super** (في fastbootd): كم ماخذ كل سلوت ونسخ التحديث (COW)، وكم الفاضي تقريباً. وقبل التفليش على قسم منطقي يتأكد إن الصورة تدخل، وإذا ما دخلت يقول لك كم ناقص ووش تقدر تحذف عشان توفر مساحة.
- **عمليات الأقسام:**
- **تفليش** صورة على القسم.
- **مسح** القسم.
@@ -97,6 +107,7 @@
- **يستخرج كل الصور أولاً، بالتوازي، ويتحقق من بصمة SHA-256 لكل صورة** قبل كتابة أي شيء.
الملف التالف لا يصل للجوال أبداً.
+- **الأقسام الناقصة:** إذا التحديث فيه أقسام منطقية مو موجودة في الجوال، ينشئها داخل super أولاً (مع توضيح مجموعة `default` في fastbootd)، ويتأكد إن الصور تدخل في super قبل الكتابة.
- تقدّم كل قسم، والمرحلة، والوقت، وسجل fastboot مباشر.
- يسألك قبل إغلاق البرنامج أثناء التفليش، حتى لا يبقى الجوال نصف مفلّش.
diff --git a/README.md b/README.md
index f003863..e415745 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,11 @@ nothing to install.
> 🆕 **A new version.** Fastboot Studio is the new generation of Fastboot Enhance: a new
> interface from top to bottom, new pages, and more protection for the phone.
>
+> **New in 2.2:**
+> - A Super space card, and a check that images fit in super before flashing.
+> - Partitions an update needs but the phone lacks are created for you.
+> - A **USB check** that finds missing drivers and EDL / BROM modes.
+>
> **New in 2.1:**
> - Slot switch protection: warns about, or refuses, a slot that is empty or will not boot.
> - Critical partitions (xbl, abl, modem, persist, preloader…) are written only after you type their name.
@@ -65,10 +70,17 @@ file onto the exe.
Everything fastboot can tell and do, on the phone that is connected.
- Lists every phone in **bootloader** or **fastbootd**; double-click to open one.
+- **USB check**: when a phone is plugged in but not listed, it reads what Windows sees and
+ says why: a missing or broken driver (with the steps to install it), or a mode fastboot
+ cannot reach (Qualcomm EDL 9008, MediaTek BROM/preloader, Unisoc or Samsung download), or a
+ phone still in Android, which it can reboot to the bootloader.
- **Basic properties**: all `getvar` values (product, slots, unlock state, current slot,
snapshot status…).
- **Partitions**: every partition with its size and whether it is logical (inside super),
with a filter by name.
+- **Super space** (fastbootd): what each slot and the update copies (COW) take in super and
+ about how much is free. Before writing to a logical partition, the app checks the image
+ fits; when it does not, it says by how much and what could make room.
- Partition actions: **flash** an image, **erase**, and for logical partitions
**create**, **resize** and **delete**. Destructive actions ask first, in red.
- **Critical partitions** (xbl, abl, tz, modem, persist, frp, preloader, nvdata…) are flashed
@@ -99,6 +111,8 @@ Flashes a full OTA (`payload.bin` or the OTA zip) to the phone in fastbootd.
- **Every image is extracted first, in parallel, and checked against its SHA-256**
before anything is written. A broken download never reaches the phone.
+- **Missing partitions** the OTA places in super are created first (with a note on
+ fastbootd's `default` group), and the images are checked to fit in super before writing.
- Per-partition progress, phase, elapsed time and a live fastboot log.
- Asks before closing the app mid-flash, since that would leave the phone half written.
@@ -275,7 +289,7 @@ decompression libraries:
| --- | --- |
| `./` | the WPF app (`net8.0-windows`) |
| `src/FastbootEnhance.Core` | payload, sparse, super and adb logic, no UI (`netstandard2.0` + `net8.0`) |
-| `tests/FastbootEnhance.Core.Tests` | xunit suite (154 tests), including byte-for-byte checks against AOSP liblp |
+| `tests/FastbootEnhance.Core.Tests` | xunit suite (172 tests), including byte-for-byte checks against AOSP liblp |
| `tools/FastbootEnhance.PayloadTool` | command line front end for the core |
| `tools/FastbootEnhance.SampleGen` | builds the sample OTA, super and Qualcomm pieces used by CI |
| `tools/FastbootEnhance.FakeFastboot`, `FakeAdb` | stand-ins for a phone, so CI can drive every page |
diff --git a/UsbScan.cs b/UsbScan.cs
new file mode 100644
index 0000000..2daf726
--- /dev/null
+++ b/UsbScan.cs
@@ -0,0 +1,45 @@
+using FastbootEnhance.Core.Usb;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Management;
+
+namespace FastbootEnhance
+{
+ /// The USB devices Windows knows about, for the USB check.
+ static class UsbScan
+ {
+ ///
+ /// A file of "name|device id|error code|class" lines read instead of Windows' list;
+ /// used by CI, which has no phone to plug in.
+ ///
+ const string StandInVariable = "FASTBOOT_STUDIO_FAKE_USB";
+
+ public static IReadOnlyList Read()
+ {
+ string standIn = Environment.GetEnvironmentVariable(StandInVariable);
+ if (!string.IsNullOrEmpty(standIn) && File.Exists(standIn))
+ return UsbDiagnosis.ParseList(File.ReadAllText(standIn));
+
+ List devices = new List();
+ using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(
+ "SELECT Name, PNPDeviceID, ConfigManagerErrorCode, PNPClass FROM Win32_PnPEntity WHERE PNPDeviceID LIKE 'USB%'"))
+ using (ManagementObjectCollection results = searcher.Get())
+ {
+ foreach (ManagementBaseObject entry in results)
+ {
+ using (entry)
+ {
+ object code = entry["ConfigManagerErrorCode"];
+ devices.Add(new PnpDevice(
+ entry["Name"] as string,
+ entry["PNPDeviceID"] as string,
+ code != null ? Convert.ToInt32(code) : 0,
+ entry["PNPClass"] as string));
+ }
+ }
+ }
+ return devices;
+ }
+ }
+}
diff --git a/docs/screenshots/04-device-list.png b/docs/screenshots/04-device-list.png
index 25c5a29..6a357d3 100644
Binary files a/docs/screenshots/04-device-list.png and b/docs/screenshots/04-device-list.png differ
diff --git a/docs/screenshots/05-usb-check.png b/docs/screenshots/05-usb-check.png
new file mode 100644
index 0000000..2f92d7b
Binary files /dev/null and b/docs/screenshots/05-usb-check.png differ
diff --git a/docs/screenshots/05-device-basic-properties.png b/docs/screenshots/06-device-basic-properties.png
similarity index 100%
rename from docs/screenshots/05-device-basic-properties.png
rename to docs/screenshots/06-device-basic-properties.png
diff --git a/docs/screenshots/06-device-partitions.png b/docs/screenshots/06-device-partitions.png
deleted file mode 100644
index 56a5fcc..0000000
Binary files a/docs/screenshots/06-device-partitions.png and /dev/null differ
diff --git a/docs/screenshots/07-confirm-erase.png b/docs/screenshots/07-confirm-erase.png
deleted file mode 100644
index 1628e7f..0000000
Binary files a/docs/screenshots/07-confirm-erase.png and /dev/null differ
diff --git a/docs/screenshots/07-device-partitions.png b/docs/screenshots/07-device-partitions.png
new file mode 100644
index 0000000..2ab6bb3
Binary files /dev/null and b/docs/screenshots/07-device-partitions.png differ
diff --git a/docs/screenshots/08-confirm-erase.png b/docs/screenshots/08-confirm-erase.png
new file mode 100644
index 0000000..4e92a74
Binary files /dev/null and b/docs/screenshots/08-confirm-erase.png differ
diff --git a/docs/screenshots/08-flash.png b/docs/screenshots/08-flash.png
deleted file mode 100644
index 45eb0f6..0000000
Binary files a/docs/screenshots/08-flash.png and /dev/null differ
diff --git a/docs/screenshots/09-create-missing.png b/docs/screenshots/09-create-missing.png
new file mode 100644
index 0000000..47b7988
Binary files /dev/null and b/docs/screenshots/09-create-missing.png differ
diff --git a/docs/screenshots/09-slot-switch-blocked.png b/docs/screenshots/09-slot-switch-blocked.png
deleted file mode 100644
index 61c22b5..0000000
Binary files a/docs/screenshots/09-slot-switch-blocked.png and /dev/null differ
diff --git a/docs/screenshots/10-critical-confirm.png b/docs/screenshots/10-critical-confirm.png
deleted file mode 100644
index 81be1cb..0000000
Binary files a/docs/screenshots/10-critical-confirm.png and /dev/null differ
diff --git a/docs/screenshots/10-flash.png b/docs/screenshots/10-flash.png
new file mode 100644
index 0000000..13c6fb3
Binary files /dev/null and b/docs/screenshots/10-flash.png differ
diff --git a/docs/screenshots/11-slot-switch-blocked.png b/docs/screenshots/11-slot-switch-blocked.png
new file mode 100644
index 0000000..9a3810d
Binary files /dev/null and b/docs/screenshots/11-slot-switch-blocked.png differ
diff --git a/docs/screenshots/11-vbmeta-question.png b/docs/screenshots/11-vbmeta-question.png
deleted file mode 100644
index e7320fe..0000000
Binary files a/docs/screenshots/11-vbmeta-question.png and /dev/null differ
diff --git a/docs/screenshots/12-critical-confirm.png b/docs/screenshots/12-critical-confirm.png
new file mode 100644
index 0000000..8023340
Binary files /dev/null and b/docs/screenshots/12-critical-confirm.png differ
diff --git a/docs/screenshots/13-boot-once-started.png b/docs/screenshots/13-boot-once-started.png
deleted file mode 100644
index 8f3d2f2..0000000
Binary files a/docs/screenshots/13-boot-once-started.png and /dev/null differ
diff --git a/docs/screenshots/13-vbmeta-question.png b/docs/screenshots/13-vbmeta-question.png
new file mode 100644
index 0000000..b551c8d
Binary files /dev/null and b/docs/screenshots/13-vbmeta-question.png differ
diff --git a/docs/screenshots/14-super-space.png b/docs/screenshots/14-super-space.png
new file mode 100644
index 0000000..ac775ee
Binary files /dev/null and b/docs/screenshots/14-super-space.png differ
diff --git a/docs/screenshots/15-super-full.png b/docs/screenshots/15-super-full.png
new file mode 100644
index 0000000..b55467b
Binary files /dev/null and b/docs/screenshots/15-super-full.png differ
diff --git a/docs/screenshots/12-boot-once-init-boot.png b/docs/screenshots/16-boot-once-init-boot.png
similarity index 100%
rename from docs/screenshots/12-boot-once-init-boot.png
rename to docs/screenshots/16-boot-once-init-boot.png
diff --git a/docs/screenshots/17-boot-once-started.png b/docs/screenshots/17-boot-once-started.png
new file mode 100644
index 0000000..f517607
Binary files /dev/null and b/docs/screenshots/17-boot-once-started.png differ
diff --git a/docs/screenshots/14-backup-fastboot.png b/docs/screenshots/18-backup-fastboot.png
similarity index 100%
rename from docs/screenshots/14-backup-fastboot.png
rename to docs/screenshots/18-backup-fastboot.png
diff --git a/docs/screenshots/18-backup-partitions.png b/docs/screenshots/18-backup-partitions.png
deleted file mode 100644
index 900e923..0000000
Binary files a/docs/screenshots/18-backup-partitions.png and /dev/null differ
diff --git a/docs/screenshots/19-backup-done.png b/docs/screenshots/19-backup-done.png
deleted file mode 100644
index 8144d79..0000000
Binary files a/docs/screenshots/19-backup-done.png and /dev/null differ
diff --git a/docs/screenshots/15-backup-fastbootd-question.png b/docs/screenshots/19-backup-fastbootd-question.png
similarity index 100%
rename from docs/screenshots/15-backup-fastbootd-question.png
rename to docs/screenshots/19-backup-fastbootd-question.png
diff --git a/docs/screenshots/16-backup-recovery-booted.png b/docs/screenshots/20-backup-recovery-booted.png
similarity index 100%
rename from docs/screenshots/16-backup-recovery-booted.png
rename to docs/screenshots/20-backup-recovery-booted.png
diff --git a/docs/screenshots/17-backup-files.png b/docs/screenshots/21-backup-files.png
similarity index 100%
rename from docs/screenshots/17-backup-files.png
rename to docs/screenshots/21-backup-files.png
diff --git a/docs/screenshots/22-backup-partitions.png b/docs/screenshots/22-backup-partitions.png
new file mode 100644
index 0000000..683ebf4
Binary files /dev/null and b/docs/screenshots/22-backup-partitions.png differ
diff --git a/docs/screenshots/23-backup-done.png b/docs/screenshots/23-backup-done.png
new file mode 100644
index 0000000..5a9530e
Binary files /dev/null and b/docs/screenshots/23-backup-done.png differ
diff --git a/docs/screenshots/20-images-empty.png b/docs/screenshots/24-images-empty.png
similarity index 100%
rename from docs/screenshots/20-images-empty.png
rename to docs/screenshots/24-images-empty.png
diff --git a/docs/screenshots/24-images-qualcomm-pieces.png b/docs/screenshots/24-images-qualcomm-pieces.png
deleted file mode 100644
index 2dd895c..0000000
Binary files a/docs/screenshots/24-images-qualcomm-pieces.png and /dev/null differ
diff --git a/docs/screenshots/21-images-super.png b/docs/screenshots/25-images-super.png
similarity index 100%
rename from docs/screenshots/21-images-super.png
rename to docs/screenshots/25-images-super.png
diff --git a/docs/screenshots/22-images-extracted.png b/docs/screenshots/26-images-extracted.png
similarity index 100%
rename from docs/screenshots/22-images-extracted.png
rename to docs/screenshots/26-images-extracted.png
diff --git a/docs/screenshots/23-images-details.png b/docs/screenshots/27-images-details.png
similarity index 100%
rename from docs/screenshots/23-images-details.png
rename to docs/screenshots/27-images-details.png
diff --git a/docs/screenshots/28-images-qualcomm-pieces.png b/docs/screenshots/28-images-qualcomm-pieces.png
new file mode 100644
index 0000000..9f445c2
Binary files /dev/null and b/docs/screenshots/28-images-qualcomm-pieces.png differ
diff --git a/docs/screenshots/28-logs.png b/docs/screenshots/28-logs.png
deleted file mode 100644
index 66bcf48..0000000
Binary files a/docs/screenshots/28-logs.png and /dev/null differ
diff --git a/docs/screenshots/25-super-empty.png b/docs/screenshots/29-super-empty.png
similarity index 100%
rename from docs/screenshots/25-super-empty.png
rename to docs/screenshots/29-super-empty.png
diff --git a/docs/screenshots/26-super-imported.png b/docs/screenshots/30-super-imported.png
similarity index 100%
rename from docs/screenshots/26-super-imported.png
rename to docs/screenshots/30-super-imported.png
diff --git a/docs/screenshots/27-super-built.png b/docs/screenshots/31-super-built.png
similarity index 100%
rename from docs/screenshots/27-super-built.png
rename to docs/screenshots/31-super-built.png
diff --git a/docs/screenshots/32-logs.png b/docs/screenshots/32-logs.png
new file mode 100644
index 0000000..30cbac7
Binary files /dev/null and b/docs/screenshots/32-logs.png differ
diff --git a/docs/screenshots/29-about.png b/docs/screenshots/33-about.png
similarity index 100%
rename from docs/screenshots/29-about.png
rename to docs/screenshots/33-about.png
diff --git a/src/FastbootEnhance.Core/Fastboot/FastbootData.cs b/src/FastbootEnhance.Core/Fastboot/FastbootData.cs
index f5eecf9..3f5269b 100644
--- a/src/FastbootEnhance.Core/Fastboot/FastbootData.cs
+++ b/src/FastbootEnhance.Core/Fastboot/FastbootData.cs
@@ -21,6 +21,9 @@ public class FastbootData
public long max_download_size;
public string snapshot_update_status;
+ /// The name of the super partition ("super-partition-name"); null when not reported.
+ public string super_partition_name;
+
/// Per slot ("a", "b"): has it booted successfully, is it marked unbootable, tries left.
public Dictionary slot_successful;
public Dictionary slot_unbootable;
@@ -99,6 +102,10 @@ public FastbootData(string real_raw_data)
snapshot_update_status = tmp[2];
break;
+ case "super-partition-name":
+ super_partition_name = tmp[2];
+ break;
+
// "(bootloader) slot-successful:a: yes"
case "slot-successful":
if (tmp.Length >= 4)
diff --git a/src/FastbootEnhance.Core/Fastboot/MissingPartitions.cs b/src/FastbootEnhance.Core/Fastboot/MissingPartitions.cs
new file mode 100644
index 0000000..b9cf318
--- /dev/null
+++ b/src/FastbootEnhance.Core/Fastboot/MissingPartitions.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+
+namespace FastbootEnhance.Core.Fastboot
+{
+ ///
+ /// Partitions an update writes that the phone does not have. Those the update places in
+ /// super can be created in fastbootd ("create-logical-partition"), which then sizes them
+ /// to their image; the others cannot be made from fastboot.
+ ///
+ public sealed class MissingPartitions
+ {
+ MissingPartitions(IReadOnlyList create, IReadOnlyList createFrom, IReadOnlyList unknown)
+ {
+ Create = create;
+ CreateFrom = createFrom;
+ Unknown = unknown;
+ }
+
+ /// Logical partitions to create, with the current slot: "odm_dlkm_a".
+ public IReadOnlyList Create { get; }
+
+ /// The update's names for , in the same order: "odm_dlkm".
+ public IReadOnlyList CreateFrom { get; }
+
+ /// Missing partitions that cannot be created from fastboot.
+ public IReadOnlyList Unknown { get; }
+
+ public bool None => Create.Count == 0 && Unknown.Count == 0;
+
+ /// What the phone reported.
+ /// The partitions the update writes.
+ /// Those of them the update places in super.
+ public static MissingPartitions Find(FastbootData data, IEnumerable written, ICollection logical)
+ {
+ List create = new List();
+ List createFrom = new List();
+ List unknown = new List();
+
+ foreach (string name in written)
+ {
+ if (data.partition_size.ContainsKey(name))
+ continue;
+ if (data.current_slot != null && data.partition_size.ContainsKey(name + "_" + data.current_slot))
+ continue;
+
+ // Only fastbootd edits super, and only partitions the update says live there.
+ if (data.fastbootd && logical.Contains(name))
+ {
+ create.Add(data.current_slot != null ? name + "_" + data.current_slot : name);
+ createFrom.Add(name);
+ }
+ else
+ {
+ unknown.Add(name);
+ }
+ }
+
+ return new MissingPartitions(create, createFrom, unknown);
+ }
+ }
+}
diff --git a/src/FastbootEnhance.Core/Fastboot/SuperSpace.cs b/src/FastbootEnhance.Core/Fastboot/SuperSpace.cs
new file mode 100644
index 0000000..289d560
--- /dev/null
+++ b/src/FastbootEnhance.Core/Fastboot/SuperSpace.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace FastbootEnhance.Core.Fastboot
+{
+ /// A logical partition and the bytes it takes in super.
+ public sealed class LogicalPartitionSize
+ {
+ internal LogicalPartitionSize(string name, long size)
+ {
+ Name = name;
+ Size = size;
+ }
+
+ public string Name { get; }
+ public long Size { get; }
+ }
+
+ ///
+ /// How the super partition is shared out, from what fastbootd reports in "getvar all":
+ /// the size of super and the size of every logical partition. The numbers are estimates:
+ /// fastboot does not report the metadata area, the alignment or the group limits, so a
+ /// little room is kept back for them.
+ ///
+ public sealed class SuperSpace
+ {
+ /// liblp places partitions on 1 MiB boundaries.
+ public const long Alignment = 1L << 20;
+
+ /// Geometry, metadata copies and the first alignment gap, rounded up.
+ public const long MetadataReserve = 4L << 20;
+
+ SuperSpace()
+ {
+ }
+
+ public string SuperName { get; private set; }
+
+ /// Bytes in super; -1 when the phone does not say (the bootloader, older phones).
+ public long SuperSize { get; private set; } = -1;
+
+ public bool Known => SuperSize > 0;
+
+ public string CurrentSlot { get; private set; }
+ public string OtherSlot { get; private set; }
+
+ public long CurrentSlotBytes { get; private set; }
+ public long OtherSlotBytes { get; private set; }
+
+ /// Copy-on-write partitions left by a Virtual A/B update ("system_b-cow").
+ public long CowBytes { get; private set; }
+
+ /// Logical partitions without a slot suffix.
+ public long UnslottedBytes { get; private set; }
+
+ /// Partitions of the other slot that hold data, largest first.
+ public IReadOnlyList OtherSlotPartitions { get; private set; } = new LogicalPartitionSize[0];
+
+ public IReadOnlyList CowPartitions { get; private set; } = new LogicalPartitionSize[0];
+
+ public long Used => CurrentSlotBytes + OtherSlotBytes + CowBytes + UnslottedBytes;
+
+ /// What is left for growing or adding partitions.
+ public long Free => Known ? Math.Max(0, SuperSize - MetadataReserve - Used) : -1;
+
+ // Logical partition (by full name) to its size in super, aligned.
+ readonly Dictionary logical = new Dictionary(StringComparer.Ordinal);
+
+ /// The bytes an image takes once written: the expanded size of a sparse image.
+ public static long WrittenSize(string path)
+ {
+ if (Images.SparseImage.IsSparse(path))
+ return Images.SparseImage.Open(path).OutputLength;
+ return new System.IO.FileInfo(path).Length;
+ }
+
+ public static long Align(long size)
+ {
+ if (size <= 0)
+ return 0;
+ return (size + Alignment - 1) / Alignment * Alignment;
+ }
+
+ public static SuperSpace From(FastbootData data)
+ {
+ SuperSpace space = new SuperSpace();
+ space.CurrentSlot = data.current_slot;
+ space.OtherSlot = data.current_slot == "a" ? "b" : data.current_slot == "b" ? "a" : null;
+ space.SuperName = string.IsNullOrEmpty(data.super_partition_name) ? "super" : data.super_partition_name;
+
+ long superSize;
+ if (data.partition_size.TryGetValue(space.SuperName, out superSize) && superSize > 0)
+ space.SuperSize = superSize;
+
+ List other = new List();
+ List cow = new List();
+
+ foreach (KeyValuePair entry in data.partition_is_logical)
+ {
+ if (entry.Value != true)
+ continue;
+ long size;
+ if (!data.partition_size.TryGetValue(entry.Key, out size) || size < 0)
+ size = 0;
+ long aligned = Align(size);
+ string name = entry.Key;
+ space.logical[name] = aligned;
+
+ if (name.EndsWith("-cow", StringComparison.Ordinal))
+ {
+ space.CowBytes += aligned;
+ if (aligned > 0)
+ cow.Add(new LogicalPartitionSize(name, aligned));
+ }
+ else if (space.CurrentSlot != null && name.EndsWith("_" + space.CurrentSlot, StringComparison.Ordinal))
+ {
+ space.CurrentSlotBytes += aligned;
+ }
+ else if (space.OtherSlot != null && name.EndsWith("_" + space.OtherSlot, StringComparison.Ordinal))
+ {
+ space.OtherSlotBytes += aligned;
+ if (aligned > 0)
+ other.Add(new LogicalPartitionSize(name, aligned));
+ }
+ else
+ {
+ space.UnslottedBytes += aligned;
+ }
+ }
+
+ space.OtherSlotPartitions = other.OrderByDescending(p => p.Size).ThenBy(p => p.Name, StringComparer.Ordinal).ToList();
+ space.CowPartitions = cow.OrderByDescending(p => p.Size).ThenBy(p => p.Name, StringComparer.Ordinal).ToList();
+ return space;
+ }
+
+ ///
+ /// The logical partition a write to lands on: the name itself
+ /// when the phone has it, otherwise the name with the current slot ("system" is
+ /// "system_a"). Null when it is not a logical partition the phone has.
+ ///
+ public string ResolveLogical(string name)
+ {
+ if (logical.ContainsKey(name))
+ return name;
+ if (CurrentSlot != null && logical.ContainsKey(name + "_" + CurrentSlot))
+ return name + "_" + CurrentSlot;
+ return null;
+ }
+
+ ///
+ /// Whether writing these images one after the other fits in super. fastbootd resizes a
+ /// logical partition to the size of the image written to it, so each write grows or
+ /// shrinks its partition; the space needed is the highest point reached on the way.
+ /// maps a partition name (with or without the slot) to the
+ /// image size; names that are not logical partitions are ignored, unless listed in
+ /// , the partitions that will be created before writing.
+ ///
+ public SuperFit Check(IEnumerable> writes, IEnumerable created = null)
+ {
+ HashSet creating = new HashSet(created ?? new string[0], StringComparer.Ordinal);
+ Dictionary sizes = new Dictionary(logical, StringComparer.Ordinal);
+ long change = 0;
+ long peak = 0;
+ List growing = new List();
+
+ foreach (KeyValuePair write in writes)
+ {
+ string target = ResolveLogical(write.Key);
+ if (target == null)
+ {
+ if (!creating.Contains(write.Key))
+ continue;
+ target = write.Key;
+ }
+
+ long before;
+ sizes.TryGetValue(target, out before);
+ long after = Align(write.Value);
+ sizes[target] = after;
+ change += after - before;
+ if (after > before)
+ growing.Add(target);
+ peak = Math.Max(peak, change);
+ }
+
+ return new SuperFit(peak, Free, growing);
+ }
+ }
+
+ /// The answer of .
+ public sealed class SuperFit
+ {
+ internal SuperFit(long needed, long free, IReadOnlyList growing)
+ {
+ Needed = needed;
+ Free = free;
+ Growing = growing;
+ }
+
+ /// Extra bytes super must have at the fullest point of the writes.
+ public long Needed { get; }
+
+ /// Free bytes now; -1 when the size of super is unknown.
+ public long Free { get; }
+
+ /// Partitions that get bigger.
+ public IReadOnlyList Growing { get; }
+
+ /// False only when it is known not to fit.
+ public bool Fits => Free < 0 || Needed <= Free;
+
+ public long Shortfall => Fits ? 0 : Needed - Free;
+ }
+}
diff --git a/src/FastbootEnhance.Core/Payload/PayloadFile.cs b/src/FastbootEnhance.Core/Payload/PayloadFile.cs
index 45db6e4..7ac283e 100644
--- a/src/FastbootEnhance.Core/Payload/PayloadFile.cs
+++ b/src/FastbootEnhance.Core/Payload/PayloadFile.cs
@@ -284,6 +284,21 @@ public PayloadPartitionInfo FindPartition(string name)
return null;
}
+ ///
+ /// The partitions the update places inside super, from its dynamic partition
+ /// metadata. Empty for updates of phones without dynamic partitions.
+ ///
+ public ISet LogicalPartitionNames()
+ {
+ HashSet names = new HashSet(StringComparer.Ordinal);
+ if (Manifest == null || Manifest.DynamicPartitionMetadata == null)
+ return names;
+ foreach (DynamicPartitionGroup group in Manifest.DynamicPartitionMetadata.Groups)
+ foreach (string name in group.PartitionNames)
+ names.Add(name);
+ return names;
+ }
+
internal PartitionUpdate FindUpdate(string name)
{
foreach (PartitionUpdate update in Manifest.Partitions)
diff --git a/src/FastbootEnhance.Core/Usb/UsbDiagnosis.cs b/src/FastbootEnhance.Core/Usb/UsbDiagnosis.cs
new file mode 100644
index 0000000..1d29fb9
--- /dev/null
+++ b/src/FastbootEnhance.Core/Usb/UsbDiagnosis.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text.RegularExpressions;
+
+namespace FastbootEnhance.Core.Usb
+{
+ /// One USB device as Windows lists it (Win32_PnPEntity).
+ public sealed class PnpDevice
+ {
+ public PnpDevice(string name, string deviceId, int errorCode, string pnpClass = null)
+ {
+ Name = name ?? "";
+ DeviceId = deviceId ?? "";
+ ErrorCode = errorCode;
+ PnpClass = pnpClass ?? "";
+ }
+
+ public string Name { get; }
+
+ /// "USB\VID_18D1&PID_4EE0\SERIAL".
+ public string DeviceId { get; }
+
+ /// ConfigManagerErrorCode: 0 works, 28 has no driver, others are driver problems.
+ public int ErrorCode { get; }
+
+ public string PnpClass { get; }
+ }
+
+ public enum UsbFindingKind
+ {
+ /// A phone Windows sees but has no driver for (code 28).
+ MissingDriver,
+
+ /// A phone whose driver is installed but not working.
+ DriverProblem,
+
+ /// Qualcomm emergency download (EDL, 9008): QFIL talks to it, fastboot cannot.
+ QualcommEdl,
+
+ /// MediaTek boot ROM or preloader: SP Flash Tool mode.
+ MediaTekDownload,
+
+ /// Unisoc (Spreadtrum) download mode.
+ UnisocDownload,
+
+ /// Samsung Download (Odin) mode; Samsung phones have no fastboot.
+ SamsungDownload,
+
+ /// A fastboot interface with a working driver.
+ FastbootReady,
+
+ /// An adb interface with a working driver: the phone is in Android or recovery.
+ AdbReady,
+ }
+
+ public sealed class UsbFinding
+ {
+ internal UsbFinding(UsbFindingKind kind, PnpDevice device, string vid, string pid)
+ {
+ Kind = kind;
+ Device = device;
+ Vid = vid;
+ Pid = pid;
+ }
+
+ public UsbFindingKind Kind { get; }
+ public PnpDevice Device { get; }
+
+ /// Vendor and product id, four upper-case hex digits; null when not in the id.
+ public string Vid { get; }
+ public string Pid { get; }
+
+ public string UsbId => Vid != null ? Vid + ":" + Pid : null;
+ }
+
+ ///
+ /// Why a phone plugged in by USB may not show up in fastboot: no driver, a broken driver,
+ /// or a mode fastboot cannot reach (EDL, MediaTek BROM, Odin...).
+ ///
+ public static class UsbDiagnosis
+ {
+ static readonly Regex UsbIdPattern = new Regex(@"VID_([0-9A-Fa-f]{4})&PID_([0-9A-Fa-f]{4})", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
+
+ // Vendors of Android phones and their chips. A device of one of them with a driver
+ // problem is very likely the phone.
+ static readonly HashSet PhoneVendors = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "18D1", // Google (the generic fastboot and adb ids)
+ "0E8D", // MediaTek
+ "05C6", // Qualcomm
+ "1782", // Unisoc / Spreadtrum
+ "2717", // Xiaomi
+ "04E8", // Samsung
+ "12D1", // Huawei
+ "2A70", // OnePlus
+ "22D9", // OPPO / realme
+ "2D95", // vivo
+ "22B8", // Motorola
+ "17EF", // Lenovo
+ "0BB4", // HTC
+ "1004", // LG
+ "0FCE", // Sony
+ "19D2", // ZTE
+ "1BBB", // TCL / Alcatel
+ "2A45", // Meizu
+ "2E04", // HMD (Nokia)
+ "2AE5", // Fairphone
+ };
+
+ static readonly string[] PhoneWords = { "android", "adb", "fastboot", "bootloader", "mtp" };
+
+ public static IReadOnlyList Diagnose(IEnumerable devices)
+ {
+ List findings = new List();
+ foreach (PnpDevice device in devices)
+ {
+ string vid = null, pid = null;
+ Match match = UsbIdPattern.Match(device.DeviceId);
+ if (match.Success)
+ {
+ vid = match.Groups[1].Value.ToUpperInvariant();
+ pid = match.Groups[2].Value.ToUpperInvariant();
+ }
+
+ UsbFindingKind? kind = Classify(device, vid, pid);
+ if (kind != null)
+ findings.Add(new UsbFinding(kind.Value, device, vid, pid));
+ }
+
+ // Problems first, then special modes, then what works.
+ findings.Sort((x, y) => x.Kind.CompareTo(y.Kind));
+ return findings;
+ }
+
+ static UsbFindingKind? Classify(PnpDevice device, string vid, string pid)
+ {
+ // Download modes are recognised by their ids, with or without a driver.
+ if (vid == "05C6" && pid == "9008")
+ return UsbFindingKind.QualcommEdl;
+ if (vid == "0E8D" && (pid == "0003" || pid == "2000" || pid == "2001"))
+ return UsbFindingKind.MediaTekDownload;
+ if (vid == "1782" && pid == "4D00")
+ return UsbFindingKind.UnisocDownload;
+ if (vid == "04E8" && pid == "685D")
+ return UsbFindingKind.SamsungDownload;
+
+ string name = device.Name.ToLowerInvariant();
+ bool looksLikePhone = (vid != null && PhoneVendors.Contains(vid)) || ContainsAny(name, PhoneWords);
+
+ if (device.ErrorCode != 0)
+ {
+ if (!looksLikePhone)
+ return null;
+ return device.ErrorCode == 28 ? UsbFindingKind.MissingDriver : UsbFindingKind.DriverProblem;
+ }
+
+ // Working interfaces, named by the Google driver and by most phone makers' drivers.
+ if (name.Contains("bootloader interface") || name.Contains("fastboot"))
+ return UsbFindingKind.FastbootReady;
+ if (name.Contains("adb interface") || name.Contains("adb device"))
+ return UsbFindingKind.AdbReady;
+ return null;
+ }
+
+ static bool ContainsAny(string text, string[] words)
+ {
+ foreach (string word in words)
+ {
+ if (text.Contains(word))
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Reads devices from lines of "name|device id|error code|class", as the app's test
+ /// stand-in writes them. Malformed lines are skipped.
+ ///
+ public static IReadOnlyList ParseList(string text)
+ {
+ List devices = new List();
+ foreach (string raw in text.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ string[] fields = raw.TrimEnd('\r').Split('|');
+ int code;
+ if (fields.Length < 3 || !int.TryParse(fields[2].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out code))
+ continue;
+ devices.Add(new PnpDevice(fields[0].Trim(), fields[1].Trim(), code, fields.Length > 3 ? fields[3].Trim() : null));
+ }
+ return devices;
+ }
+ }
+}
diff --git a/tests/FastbootEnhance.Core.Tests/SuperSpaceTests.cs b/tests/FastbootEnhance.Core.Tests/SuperSpaceTests.cs
new file mode 100644
index 0000000..eb784c7
--- /dev/null
+++ b/tests/FastbootEnhance.Core.Tests/SuperSpaceTests.cs
@@ -0,0 +1,163 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using FastbootEnhance.Core.Fastboot;
+using Xunit;
+
+namespace FastbootEnhance.Core.Tests
+{
+ public class SuperSpaceTests
+ {
+ const long MiB = 1L << 20;
+ const long GiB = 1L << 30;
+
+ // A Virtual A/B phone in fastbootd: 8 GiB super, slot a in use, slot b empty.
+ static FastbootData Phone(string extra = "", bool fastbootd = true)
+ {
+ return new FastbootData(
+ "(bootloader) is-userspace:" + (fastbootd ? "yes" : "no") + "\n" +
+ "(bootloader) current-slot:a\n" +
+ "(bootloader) super-partition-name:super\n" +
+ "(bootloader) partition-size:super:0x200000000\n" +
+ "(bootloader) is-logical:super:no\n" +
+ "(bootloader) partition-size:boot_a:0x4000000\n" +
+ "(bootloader) is-logical:boot_a:no\n" +
+ "(bootloader) partition-size:system_a:0x80000000\n" +
+ "(bootloader) is-logical:system_a:yes\n" +
+ "(bootloader) partition-size:vendor_a:0x40000000\n" +
+ "(bootloader) is-logical:vendor_a:yes\n" +
+ "(bootloader) partition-size:system_b:0x0\n" +
+ "(bootloader) is-logical:system_b:yes\n" +
+ extra);
+ }
+
+ static KeyValuePair W(string name, long size)
+ {
+ return new KeyValuePair(name, size);
+ }
+
+ [Fact]
+ public void Super_is_split_into_slots_cow_and_free_space()
+ {
+ SuperSpace space = SuperSpace.From(Phone(
+ "(bootloader) partition-size:product_b:0x20000000\n(bootloader) is-logical:product_b:yes\n" +
+ "(bootloader) partition-size:system_a-cow:0x10000000\n(bootloader) is-logical:system_a-cow:yes\n"));
+
+ Assert.True(space.Known);
+ Assert.Equal(8 * GiB, space.SuperSize);
+ Assert.Equal(3 * GiB, space.CurrentSlotBytes);
+ Assert.Equal(512 * MiB, space.OtherSlotBytes);
+ Assert.Equal(256 * MiB, space.CowBytes);
+ Assert.Equal(8 * GiB - SuperSpace.MetadataReserve - 3 * GiB - 768 * MiB, space.Free);
+ Assert.Equal("product_b", space.OtherSlotPartitions.Single().Name);
+ Assert.Equal("system_a-cow", space.CowPartitions.Single().Name);
+ }
+
+ [Fact]
+ public void Sizes_are_counted_on_1_MiB_boundaries()
+ {
+ Assert.Equal(0, SuperSpace.Align(0));
+ Assert.Equal(MiB, SuperSpace.Align(1));
+ Assert.Equal(MiB, SuperSpace.Align(MiB));
+ Assert.Equal(2 * MiB, SuperSpace.Align(MiB + 4096));
+ }
+
+ [Fact]
+ public void Super_size_is_unknown_without_it_and_nothing_is_refused()
+ {
+ SuperSpace space = SuperSpace.From(new FastbootData(
+ "(bootloader) is-userspace:yes\n(bootloader) current-slot:a\n" +
+ "(bootloader) partition-size:system_a:0x80000000\n(bootloader) is-logical:system_a:yes\n"));
+ Assert.False(space.Known);
+ Assert.Equal(-1, space.Free);
+ Assert.True(space.Check(new[] { W("system", 100 * GiB) }).Fits);
+ }
+
+ [Fact]
+ public void A_bigger_image_fits_when_super_has_room()
+ {
+ SuperSpace space = SuperSpace.From(Phone());
+ SuperFit fit = space.Check(new[] { W("system", 3 * GiB) });
+ Assert.True(fit.Fits);
+ Assert.Equal(GiB, fit.Needed);
+ Assert.Equal(new[] { "system_a" }, fit.Growing);
+ }
+
+ [Fact]
+ public void An_image_too_big_for_super_does_not_fit()
+ {
+ SuperSpace space = SuperSpace.From(Phone());
+ SuperFit fit = space.Check(new[] { W("system_a", 8 * GiB) });
+ Assert.False(fit.Fits);
+ Assert.Equal(6 * GiB - space.Free, fit.Shortfall);
+ }
+
+ [Fact]
+ public void The_fullest_point_counts_not_only_the_total()
+ {
+ // vendor grows by 5 GiB first and only then system shrinks by 2 GiB: at the
+ // fullest point 5 GiB more is needed, although the total change is 3 GiB.
+ SuperSpace space = SuperSpace.From(Phone());
+ SuperFit fit = space.Check(new[] { W("vendor", 6 * GiB), W("system", 0) });
+ Assert.False(fit.Fits);
+ Assert.Equal(5 * GiB, fit.Needed);
+
+ SuperFit shrinkFirst = space.Check(new[] { W("system", 0), W("vendor", 6 * GiB) });
+ Assert.True(shrinkFirst.Fits);
+ Assert.Equal(3 * GiB, shrinkFirst.Needed);
+ }
+
+ [Fact]
+ public void Partitions_outside_super_and_unknown_names_are_ignored_unless_created()
+ {
+ SuperSpace space = SuperSpace.From(Phone());
+ Assert.Equal(0, space.Check(new[] { W("boot", 64 * MiB), W("odm_dlkm", 64 * MiB) }).Needed);
+ Assert.Equal(64 * MiB, space.Check(new[] { W("odm_dlkm_a", 64 * MiB) }, new[] { "odm_dlkm_a" }).Needed);
+ }
+
+ [Fact]
+ public void A_sparse_image_counts_at_its_written_size()
+ {
+ string dir = Path.Combine(Path.GetTempPath(), "fbe-written-" + System.Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(dir);
+ try
+ {
+ string sparse = Path.Combine(dir, "big.img");
+ const uint blocks = (uint)((12L << 30) / 4096);
+ using (BinaryWriter w = new BinaryWriter(File.Create(sparse)))
+ {
+ w.Write(0xED26FF3Au); w.Write((ushort)1); w.Write((ushort)0); w.Write((ushort)28); w.Write((ushort)12);
+ w.Write(4096u); w.Write(blocks); w.Write(1u); w.Write(0u);
+ w.Write((ushort)0xCAC3); w.Write((ushort)0); w.Write(blocks); w.Write(12u);
+ }
+ Assert.Equal(12 * GiB, SuperSpace.WrittenSize(sparse));
+
+ string raw = Path.Combine(dir, "raw.img");
+ File.WriteAllBytes(raw, new byte[12345]);
+ Assert.Equal(12345, SuperSpace.WrittenSize(raw));
+ }
+ finally
+ {
+ Directory.Delete(dir, true);
+ }
+ }
+
+ [Fact]
+ public void Missing_logical_partitions_can_be_created_only_in_fastbootd()
+ {
+ string[] written = { "boot", "system", "odm_dlkm", "modem" };
+ HashSet logical = new HashSet { "system", "odm_dlkm" };
+
+ MissingPartitions inFastbootd = MissingPartitions.Find(Phone(), written, logical);
+ Assert.Equal(new[] { "odm_dlkm_a" }, inFastbootd.Create);
+ Assert.Equal(new[] { "odm_dlkm" }, inFastbootd.CreateFrom);
+ Assert.Equal(new[] { "modem" }, inFastbootd.Unknown);
+
+ MissingPartitions inBootloader = MissingPartitions.Find(Phone(fastbootd: false), written, logical);
+ Assert.Empty(inBootloader.Create);
+ Assert.Equal(new[] { "odm_dlkm", "modem" }, inBootloader.Unknown);
+
+ Assert.True(MissingPartitions.Find(Phone(), new[] { "boot", "system" }, logical).None);
+ }
+ }
+}
diff --git a/tests/FastbootEnhance.Core.Tests/UsbDiagnosisTests.cs b/tests/FastbootEnhance.Core.Tests/UsbDiagnosisTests.cs
new file mode 100644
index 0000000..1490f5f
--- /dev/null
+++ b/tests/FastbootEnhance.Core.Tests/UsbDiagnosisTests.cs
@@ -0,0 +1,69 @@
+using System.Linq;
+using FastbootEnhance.Core.Usb;
+using Xunit;
+
+namespace FastbootEnhance.Core.Tests
+{
+ public class UsbDiagnosisTests
+ {
+ [Fact]
+ public void A_phone_without_a_driver_is_found_by_vendor_or_name()
+ {
+ var findings = UsbDiagnosis.Diagnose(new[]
+ {
+ new PnpDevice("Android", @"USB\VID_0E8D&PID_201C\0123456789", 28),
+ new PnpDevice("Unknown Device", @"USB\VID_2717&PID_FF48\abc", 28),
+ new PnpDevice("Fastboot", @"USB\VID_1234&PID_5678\x", 28),
+ new PnpDevice("USB Keyboard", @"USB\VID_046D&PID_C31C\x", 28),
+ });
+ Assert.Equal(3, findings.Count);
+ Assert.All(findings, f => Assert.Equal(UsbFindingKind.MissingDriver, f.Kind));
+ Assert.Equal("0E8D:201C", findings.First(f => f.Device.Name == "Android").UsbId);
+ }
+
+ [Fact]
+ public void A_broken_driver_is_not_a_missing_one()
+ {
+ var finding = UsbDiagnosis.Diagnose(new[]
+ {
+ new PnpDevice("Android Bootloader Interface", @"USB\VID_18D1&PID_4EE0\x", 10),
+ }).Single();
+ Assert.Equal(UsbFindingKind.DriverProblem, finding.Kind);
+ }
+
+ [Theory]
+ [InlineData(@"USB\VID_05C6&PID_9008\5&1", UsbFindingKind.QualcommEdl)]
+ [InlineData(@"USB\VID_0E8D&PID_0003\5&1", UsbFindingKind.MediaTekDownload)]
+ [InlineData(@"USB\VID_0e8d&pid_2000\5&1", UsbFindingKind.MediaTekDownload)]
+ [InlineData(@"USB\VID_1782&PID_4D00\5&1", UsbFindingKind.UnisocDownload)]
+ [InlineData(@"USB\VID_04E8&PID_685D\5&1", UsbFindingKind.SamsungDownload)]
+ public void Download_modes_are_recognised_with_or_without_a_driver(string id, UsbFindingKind kind)
+ {
+ Assert.Equal(kind, UsbDiagnosis.Diagnose(new[] { new PnpDevice("x", id, 0) }).Single().Kind);
+ Assert.Equal(kind, UsbDiagnosis.Diagnose(new[] { new PnpDevice("x", id, 28) }).Single().Kind);
+ }
+
+ [Fact]
+ public void Working_interfaces_are_reported_after_the_problems()
+ {
+ var findings = UsbDiagnosis.Diagnose(new[]
+ {
+ new PnpDevice("Android Composite ADB Interface", @"USB\VID_18D1&PID_4EE7&MI_01\6", 0),
+ new PnpDevice("Android Bootloader Interface", @"USB\VID_18D1&PID_4EE0\x", 0),
+ new PnpDevice("Android", @"USB\VID_18D1&PID_D001\x", 28),
+ new PnpDevice("USB Mass Storage Device", @"USB\VID_0781&PID_5567\x", 0),
+ });
+ Assert.Equal(new[] { UsbFindingKind.MissingDriver, UsbFindingKind.FastbootReady, UsbFindingKind.AdbReady },
+ findings.Select(f => f.Kind));
+ }
+
+ [Fact]
+ public void The_stand_in_list_is_read_line_by_line()
+ {
+ var devices = UsbDiagnosis.ParseList("Android|USB\\VID_0E8D&PID_201C\\1|28|\r\nbroken line\nPreloader|USB\\VID_0E8D&PID_2000\\2|0|Ports\n");
+ Assert.Equal(2, devices.Count);
+ Assert.Equal(28, devices[0].ErrorCode);
+ Assert.Equal("Ports", devices[1].PnpClass);
+ }
+ }
+}
diff --git a/tools/FastbootEnhance.FakeFastboot/Program.cs b/tools/FastbootEnhance.FakeFastboot/Program.cs
index 961fe0e..3c5fa72 100644
--- a/tools/FastbootEnhance.FakeFastboot/Program.cs
+++ b/tools/FastbootEnhance.FakeFastboot/Program.cs
@@ -12,7 +12,8 @@ static class Program
{
const string Serial = "FBE0SAMPLE01";
- // Partitions the sample OTA contains, with sizes large enough to take its images.
+ // Partitions the sample OTA contains, with sizes large enough to take its images. odm
+ // is missing on purpose: the app has to offer to create it before flashing the OTA.
static readonly (string name, long size, bool logical)[] Partitions =
{
("boot_a", 0x4000000, false), ("boot_b", 0x4000000, false),
@@ -28,7 +29,6 @@ static readonly (string name, long size, bool logical)[] Partitions =
("product_a", 0x8000000, true), ("product_b", 0, true),
("vendor_a", 0x6000000, true), ("vendor_b", 0, true),
("vendor_dlkm_a", 0x2000000, true), ("vendor_dlkm_b", 0, true),
- ("odm_a", 0x800000, true), ("odm_b", 0, true),
("super", 0x240000000, false),
("metadata", 0x1000000, false),
("misc", 0x100000, false),
diff --git a/tools/FastbootEnhance.SampleGen/Program.cs b/tools/FastbootEnhance.SampleGen/Program.cs
index 592bd46..5161827 100644
--- a/tools/FastbootEnhance.SampleGen/Program.cs
+++ b/tools/FastbootEnhance.SampleGen/Program.cs
@@ -126,6 +126,28 @@ static void WriteRecoveryImage(string outDir)
string initPath = Path.Combine(outDir, "init_boot-sample.img");
File.WriteAllBytes(initPath, initBoot);
Console.WriteLine("wrote " + initPath);
+
+ // A sparse image of 12 GiB that is one "don't care" chunk: 40 bytes on disk, far
+ // more than the stand-in phone's super can take once written.
+ const uint blockSize = 4096;
+ const uint blocks = (uint)((12L << 30) / blockSize);
+ using (BinaryWriter big = new BinaryWriter(File.Create(Path.Combine(outDir, "big-sparse.img"))))
+ {
+ big.Write(0xED26FF3Au); // magic
+ big.Write((ushort)1); // major version
+ big.Write((ushort)0); // minor version
+ big.Write((ushort)28); // file header size
+ big.Write((ushort)12); // chunk header size
+ big.Write(blockSize);
+ big.Write(blocks); // total blocks
+ big.Write(1u); // total chunks
+ big.Write(0u); // checksum
+ big.Write((ushort)0xCAC3); // DONT_CARE
+ big.Write((ushort)0);
+ big.Write(blocks); // chunk size in blocks
+ big.Write(12u); // chunk header only
+ }
+ Console.WriteLine("wrote big-sparse.img");
}
///