Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7e3899b
Google USB driver: Settings card and USB check offer it; downloaded f…
ifoknr Nov 2, 2026
6034e00
Odin flashing in Core: file part size, total size and file transfers …
ifoknr Nov 2, 2026
af3efad
Samsung flashing in the Download mode window: files chosen, read and …
ifoknr Nov 2, 2026
1694f33
v2.9.0 in the sidebar and About; the flash plan is checked again agai…
ifoknr Nov 2, 2026
f558ed8
Update app screenshots [skip ci]
github-actions[bot] Nov 2, 2026
856bb9b
Arabic: spaces around the firmware part names, so they read in order
ifoknr Nov 2, 2026
995a914
Review of the whole app: safety checks tightened, crashes on damaged …
ifoknr Nov 8, 2026
e7c0a76
Odin size limit from the PIT's storage type, Odin flash order, more T…
ifoknr Nov 9, 2026
299a492
One summary before a payload flash, the phone named in every confirma…
ifoknr Nov 9, 2026
bb1142d
Glass look: palettes, 16 px cards, grouped rail, device bar, status b…
ifoknr Nov 9, 2026
94db77e
Pages in the glass look: Samsung match row, empty states, Root steps,…
ifoknr Nov 9, 2026
54412e7
Dialogs in 16 px glass; Settings offers the complete languages only
ifoknr Nov 9, 2026
1d4face
Help, changelog and README for the glass look, the summary, the log f…
ifoknr Nov 9, 2026
b347cb4
Test: the rail reads the version from the code, not a literal
ifoknr Nov 9, 2026
dc62c83
Rail pages dock at the top; the bars' text takes the theme colour; Bu…
ifoknr Nov 9, 2026
d185657
Version 3.0.0: the glass look's notes under v3.0.0 (no tag, no release)
ifoknr Nov 9, 2026
705df52
Chinese and Japanese: every string translated
ifoknr Nov 9, 2026
47f06bc
Korean complete; Chinese, Japanese and Korean offered again; shorter …
ifoknr Nov 9, 2026
8c7a58b
Samsung: the Download-mode button stays in the header, firmware open …
ifoknr Nov 9, 2026
167b01b
The sky behind the window is a plain gradient: a DrawingBrush drew bl…
ifoknr Nov 9, 2026
91954cf
Update app screenshots [skip ci]
ifoknr Nov 9, 2026
5a19983
Dark theme: solid charcoal (IFOKNR's pick A) instead of see-through g…
ifoknr Nov 9, 2026
8e01d8a
Mica only when asked for and with graphics-card drawing
ifoknr Nov 9, 2026
a675933
Light theme: solid white cards on a pale grey page, to match the soli…
ifoknr Nov 9, 2026
e8476c1
Update app screenshots [skip ci]
ifoknr Nov 9, 2026
a3fda0f
About: every link button draws its icon (a template, not one shared P…
ifoknr Nov 9, 2026
51cc338
Chips are rounded rectangles, not ovals; the Device page no longer re…
ifoknr Nov 9, 2026
eb77ed2
Update app screenshots [skip ci]
ifoknr Nov 9, 2026
ec089e0
Merge remote-tracking branch 'origin/master' into design-3.0
ifoknr Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions .github/scripts/screenshots.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -347,27 +347,29 @@ if ($ExpectedFlashes -gt 0) {
# The file dialog takes a moment to go away; it must not be mistaken for the result.
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"
# One summary before the flash: the phone, the file, and every point to know. Here the
# stand-in has no odm (created in super first) and the six physical partitions also go to
# slot b. A point that could cost data adds an "I have read this" box; tick it if it is there.
$summary = Wait-For { Find-Dialog } 30 "the flash summary"
Start-Sleep -Milliseconds 600
Save-Window ('{0:D2}-create-missing' -f $i); $i++
Answer-Dialog $create 'Yes'

$slots = Wait-For { Find-Dialog } 30 "the both-slots question"
Start-Sleep -Milliseconds 600
Save-Window ('{0:D2}-both-slots' -f $i); $i++
Answer-Dialog $slots '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
# UI Automation, so the fastboot log below is what proves the flash.
$done = Wait-For { Find-Dialog } 240 "flashing to finish"
Save-Window ('{0:D2}-flash-summary' -f $i); $i++
$i++ # the both-slots question used to be its own screen; the number is kept so later links hold
$ackCond = New-Object System.Windows.Automation.PropertyCondition($UIA::ControlTypeProperty, $Type::CheckBox)
$ack = @($summary.FindAll($Scope::Descendants, $ackCond)) | Select-Object -First 1
if ($null -ne $ack) { $ack.GetCurrentPattern([System.Windows.Automation.TogglePattern]::Pattern).Toggle() }
Answer-Dialog $summary 'Flash'

# The result is a strip on the Flash page (an error is still a box). The fastboot log below
# is what proves the flash.
$result = Wait-For { $d = Find-Dialog; if ($null -ne $d) { $d } else { Shown $root 'flash_result_title' } } 240 "flashing to finish"
if ($result.Current.ControlType -eq $Type::Window) {
$message = ($result.FindAll($Scope::Descendants, [System.Windows.Automation.Condition]::TrueCondition) |
ForEach-Object { $_.Current.Name } | Where-Object { $_ }) -join ' | '
Fail "flashing ended with a box: $message"
}
Start-Sleep -Milliseconds 800
$message = ($done.FindAll($Scope::Descendants, [System.Windows.Automation.Condition]::TrueCondition) |
ForEach-Object { $_.Current.Name } | Where-Object { $_ }) -join ' | '
Write-Host "dialog after flashing: $message"
Write-Host "after flashing: $((By-Id $root 'flash_result_title').Current.Name)"
Save-Window ('{0:D2}-flash' -f $i); $i++
Close-Dialog $done

Remove-Item $fastbootRefuse -ErrorAction SilentlyContinue
$flashes = @(Get-Content $fakeLog | Where-Object { $_ -match ' flash ' }).Count
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
name: core library and tests (linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 8.0.x

Expand All @@ -42,9 +42,9 @@ jobs:
name: wpf app (windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 8.0.x

Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
Write-Host "Samsung's installer: $pos"
if (-not ($pos -match 'samsung=True')) { throw "Samsung's own installer did not pass the signature check" }

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: FastbootStudio-win-x64
path: publish/
Expand Down
27 changes: 15 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ on:
type: boolean
default: false

# Only the release job writes to the repository (the release, the tag); the rest read.
permissions:
contents: write
contents: read

env:
DOTNET_NOLOGO: true
Expand All @@ -36,9 +37,9 @@ jobs:
name: core tests (linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 8.0.x

Expand All @@ -50,7 +51,7 @@ jobs:
needs: test
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Check the version
shell: bash
Expand All @@ -75,7 +76,7 @@ jobs:
fi
done

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 8.0.x

Expand Down Expand Up @@ -104,15 +105,15 @@ jobs:
- name: Upload FastbootStudio.exe for signing
id: unsigned
if: steps.signpath.outputs.enabled == 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: unsigned-exe
path: publish/FastbootStudio.exe
if-no-files-found: error

- name: Sign FastbootStudio.exe
if: steps.signpath.outputs.enabled == 'true'
uses: signpath/github-action-submit-signing-request@v3
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
Expand Down Expand Up @@ -148,7 +149,7 @@ jobs:
[System.IO.File]::WriteAllText("$zip.sha256", "$hash $zip`n")
Get-Content "$zip.sha256"

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-zip
path: |
Expand All @@ -160,10 +161,12 @@ jobs:
name: publish and announce
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-zip
path: out
Expand Down Expand Up @@ -217,7 +220,7 @@ jobs:
if: env.PRE != 'true'
run: bash .github/scripts/winget-manifests.sh "$RELEASE_VERSION" "$FILE" winget

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: env.PRE != 'true'
with:
name: winget-manifests
Expand All @@ -236,7 +239,7 @@ jobs:
- name: Submit to winget
if: steps.winget.outputs.enabled == 'true'
continue-on-error: true
uses: vedantmgoyal9/winget-releaser@v2
uses: vedantmgoyal9/winget-releaser@b7370d4d152efdce803a26cc6ca9eee58cb2ad54 # v2
with:
identifier: IFOKNR.FastbootStudio
release-tag: ${{ env.RELEASE_VERSION }}
Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ jobs:
capture:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0 # full history so the commit step can rebase

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 8.0.x

Expand Down Expand Up @@ -60,7 +60,7 @@ jobs:
shell: pwsh
run: ./.github/scripts/screenshots.ps1 -App publish/FastbootStudio.exe -Payload sample/sample-ota.zip -Out docs/screenshots -ExpectedFlashes 12 -ExpectedBackups 20 -Arabic

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: screenshots
Expand All @@ -71,14 +71,16 @@ jobs:
- name: Commit screenshots
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
shell: pwsh
env:
BRANCH: ${{ github.ref_name }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.name "IFOKNR"
git config user.email "253658289+ifoknr@users.noreply.github.com"
git add docs/screenshots
git diff --cached --quiet
if ($LASTEXITCODE -ne 0) {
git commit -m "Update app screenshots [skip ci]"
# The branch may have moved while the app was being captured.
git pull --rebase origin ${{ github.ref_name }}
git push origin HEAD:${{ github.ref_name }}
git pull --rebase origin $env:BRANCH
git push origin HEAD:$env:BRANCH
}
2 changes: 1 addition & 1 deletion .github/workflows/telegram.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Get the release
env:
Expand Down
16 changes: 12 additions & 4 deletions AdbFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public static void ReadConnected()
}
}

/// <summary>The Settings key: "on" when a connected phone may be asked for root on its own.</summary>
public const string RootProbeKey = "root_probe";

/// <summary>Whether a phone is asked for root when it connects; off until turned on in Settings.</summary>
public static bool RootProbe => Settings.Get(RootProbeKey) == "on";

/// <summary>
/// Asks the phone and stores what it said. Blocks, so call it on a worker thread. A phone
/// that does not answer is simply not remembered.
Expand All @@ -72,10 +78,12 @@ public static DeviceFacts Read(string serial)
Settings.Set(FastbootUI.DeviceFactsKey, DeviceFacts.Remember(Settings.Get(FastbootUI.DeviceFactsKey), facts));

// The Companion lives under /data/adb, which only root can reach. Ask su only when
// the phone has one; Magisk shows a prompt the first time, KernelSU and APatch
// answer from their own allow list.
Adb.Result su = Adb.Run(AdbCommand.Shell(serial, "command -v su"), Adb.ShortCommand);
if (su.Succeeded && su.Output.Trim().Length > 0)
// the phone has one and the person turned the question on in Settings; Magisk
// shows a prompt the first time, KernelSU and APatch answer from their own allow list.
Adb.Result su = RootProbe
? Adb.Run(AdbCommand.Shell(serial, "command -v su"), Adb.ShortCommand)
: null;
if (su != null && su.Succeeded && su.Output.Trim().Length > 0)
{
LogStore.Append("ADB device " + serial + ": asking for root to read Fastboot Studio Companion "
+ "(allow Shell in the root manager, or ignore this)");
Expand Down
Binary file added Assets/icon-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Assets/icon-themed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions BackupUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ static class BackupUI

static string lastOutputFolder;

/// <summary>True while the poller sets the list's selection itself: the handler then stays out.</summary>
static bool choosing;

static volatile bool busy;
static volatile bool pageVisible;
static CancellationTokenSource cancel;
Expand Down Expand Up @@ -79,6 +82,8 @@ public static void init()

W.backup_devices.SelectionChanged += delegate
{
if (choosing)
return;
AdbDevice picked = W.backup_devices.SelectedItem as AdbDevice;
if (picked != null && (current == null || picked.Serial != current.Serial))
selectDevice(picked);
Expand Down Expand Up @@ -221,7 +226,9 @@ static void showDevices(List<AdbDevice> latest)
return;
}

choosing = true;
W.backup_devices.SelectedItem = keep;
choosing = false;
if (current == null || keep.Serial != current.Serial || keep.State != current.State)
selectDevice(keep);
}
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@

<!-- The "## vX.Y.Z" section is the release notes and the Telegram post (.github/scripts/telegram-release.sh): Arabic bullets, then "**English**" and the same bullets in English. Keep it short. -->

## v3.0.0
- شكل زجاجي جديد وأيقونة جديدة: رمادي زجاجي بالليلي، ثلجي بالنهاري
- القائمة بمجموعات، وشريط فوق يبيّن الجوال ووضعه، وشريط حالة تحت
- قبل الفلاش ملخص واحد فيه الجوال وكل النقاط، والنتيجة على الصفحة بدل نافذة
- سجل لكل فلاش ينحفظ كملف، وزر إيقاف للأمر المعلّق
- أودين: الحد من نوع الذاكرة، والترتيب BL ثم AP ثم CP ثم CSC
- اختصارات كيبورد، والصيني والياباني والكوري كاملين

**English**
- A new glass look and icon: grey glass in dark, frosted in light
- Pages in groups, a bar above with the phone and its mode, a status bar below
- One summary before a flash with the phone and every point; the result on the page
- A log file per flash, and a Stop for a hung command
- Odin: limits from the storage type; BL, AP, CP, CSC order
- Keyboard shortcuts; Chinese, Japanese and Korean complete

## v2.9.0
- جديد: فلاش سامسونج من التطبيق في وضع التحميل: صورة وحدة، tar من Magisk، أو الروم كامل
- قبل ما يكتب: يفحص MD5 والـ lz4، وجدول PIT والموديل، ويرفض EFS وروم موديل ثاني
Expand Down
33 changes: 33 additions & 0 deletions DarkTitleBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ static class DarkTitleBar
const int DWMWA_BORDER_COLOR = 34;
const int DWMWA_CAPTION_COLOR = 35;
const int DWMWA_TEXT_COLOR = 36;
const int DWMWA_SYSTEMBACKDROP_TYPE = 38;
const int DWMSBT_NONE = 1;
const int DWMSBT_MAINWINDOW = 2; // Mica

/// <summary>The Settings key of the glass backdrop: "off" to keep the window's own gradient.</summary>
public const string BackdropKey = "backdrop";

/// <summary>Windows 11 22H2 and later draw Mica; older systems have nothing to turn on.</summary>
public static bool BackdropAvailable => Environment.OSVersion.Version.Build >= 22621;

/// <summary>
/// Off unless turned on in Settings, and only with graphics-card drawing: under software
/// drawing (the app's default) Windows paints nothing behind a clear window, so the page
/// turned black.
/// </summary>
public static bool BackdropWanted => BackdropAvailable && Rendering.Chosen == Rendering.Mode.Gpu
&& Settings.Get(BackdropKey) == "on";

/// <summary>COLORREF (0x00BBGGRR) of one of the theme's brushes: Bg, Line or Text.</summary>
static int ColorRef(string key)
Expand Down Expand Up @@ -49,6 +66,22 @@ public static void Apply(Window window)
DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref caption, sizeof(int));
DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, ref border, sizeof(int));
DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, ref text, sizeof(int));

// Mica: the desktop shows through the window, blurred. The window's own
// background has to be clear for it to show, so the sky brush goes when it is on.
if (BackdropAvailable)
{
bool mica = BackdropWanted;
int backdrop = mica ? DWMSBT_MAINWINDOW : DWMSBT_NONE;
if (DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, ref backdrop, sizeof(int)) == 0)
{
HwndSource source = HwndSource.FromHwnd(hwnd);
if (source?.CompositionTarget != null)
source.CompositionTarget.BackgroundColor = mica ? System.Windows.Media.Colors.Transparent
: ((System.Windows.Media.SolidColorBrush)Palette.Get("Bg")).Color;
window.Background = mica ? System.Windows.Media.Brushes.Transparent : Palette.Get("SkyBrush");
}
}
}
catch (DllNotFoundException)
{
Expand Down
8 changes: 4 additions & 4 deletions DriverWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
SizeToContent="Height" Width="596" ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Foreground="{StaticResource Text}" FontFamily="{StaticResource UiFont}" FontSize="13" UseLayoutRounding="True">
<!-- Samsung's USB driver: download from Samsung, check Samsung signed it, run its installer. -->
<Border Margin="18" Background="{StaticResource Surface}" BorderBrush="{StaticResource Line}"
BorderThickness="1" CornerRadius="12">
<Border Margin="18" Background="{StaticResource Surface}" BorderBrush="{StaticResource GlassLine2}"
BorderThickness="1" CornerRadius="16">
<Border.Effect>
<DropShadowEffect BlurRadius="26" ShadowDepth="6" Opacity="0.55" Color="#000000"/>
</Border.Effect>
Expand All @@ -18,7 +18,7 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Border x:Name="strip" Height="3" VerticalAlignment="Top" CornerRadius="11,11,0,0" Background="{StaticResource Accent}"/>
<Border x:Name="strip" Height="3" VerticalAlignment="Top" CornerRadius="15,15,0,0" Background="{StaticResource Accent}"/>

<DockPanel x:Name="header" Margin="22,20,22,0" Background="Transparent">
<Border x:Name="badge" DockPanel.Dock="Left" Width="34" Height="34" CornerRadius="10" BorderThickness="1"
Expand All @@ -39,7 +39,7 @@
</StackPanel>

<Border Grid.Row="2" Margin="0,18,0,0" Padding="22,14" Background="{StaticResource Bg}"
BorderBrush="{StaticResource Line}" BorderThickness="0,1,0,0" CornerRadius="0,0,11,11">
BorderBrush="{StaticResource Line}" BorderThickness="0,1,0,0" CornerRadius="0,0,15,15">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="secondary" MinWidth="96" Margin="0,0,10,0"/>
<Button x:Name="primary" MinWidth="120" Style="{StaticResource AccentButton}" IsDefault="True"/>
Expand Down
Loading
Loading