Skip to content
Draft
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
34b6c01
v2.8.0 (work in progress): help on every page, welcome tour, What's n…
ifoknr Oct 27, 2026
922471c
Help for every page in English and Arabic; Samsung driver signature c…
ifoknr Oct 27, 2026
0975185
Tour check: a green mark uses AccentSoft (there is no OkSoft brush)
ifoknr Oct 27, 2026
40e9b20
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
107570d
Tour check: device names keep their brackets in Arabic
ifoknr Oct 27, 2026
a7d5231
Review fixes: the driver installer is checked and run while held open…
ifoknr Oct 27, 2026
f136dc8
Merge branch 'v2.8.0' of https://github.com/ifoknr/fastbootstudio int…
ifoknr Oct 27, 2026
2d9ede8
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
1b26584
README: which phones work, what unlocking takes on each, what works w…
ifoknr Oct 27, 2026
8cb5ed7
Samsung page: Knox reading 0x0 on an unlocked bootloader is flagged, …
ifoknr Oct 27, 2026
0e32ec2
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
51ac69c
Odin: read a Samsung phone in Download mode (handshake, session, PIT,…
ifoknr Oct 27, 2026
71b52e6
Samsung page: Download mode window reads a phone over Samsung's drive…
ifoknr Oct 27, 2026
b4d5428
Odin: a phone that refuses to restart into Download mode is restarted…
ifoknr Oct 27, 2026
fe2235e
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
bdfff77
Merge branch 'odin' of https://github.com/ifoknr/fastbootstudio into …
ifoknr Oct 27, 2026
f92ac5f
Download mode window: Arabic lines of the reading keep their own order
ifoknr Oct 27, 2026
a96deba
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
49e6f64
v2.8.0 notes and README: Download mode reading (English and Arabic)
ifoknr Oct 27, 2026
943b132
Update app screenshots [skip ci]
github-actions[bot] Oct 27, 2026
5d5b64b
Numbers stay 0-9 in every language: Western digits and a dot whatever…
ifoknr Oct 28, 2026
e063826
Western digits: set on each window instead of overriding WPF's metada…
ifoknr Oct 28, 2026
d5eaf8e
Update app screenshots [skip ci]
github-actions[bot] Oct 28, 2026
62066d7
About: IFOKNR in the Swamp Ninja lettering, as a picture in the theme…
ifoknr Oct 28, 2026
c63fb29
Merge branch 'v2.8.0' of https://github.com/ifoknr/fastbootstudio int…
ifoknr Oct 28, 2026
c84b466
Update app screenshots [skip ci]
github-actions[bot] Oct 28, 2026
b418c57
New icon (phone and bolt on violet) and the app name in the Prixma Bo…
ifoknr Oct 28, 2026
b9fab81
Update app screenshots [skip ci]
github-actions[bot] Oct 28, 2026
0050192
New icon: the bolt on a circuit board in the app's teal (simpler draw…
ifoknr Oct 28, 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
126 changes: 126 additions & 0 deletions .github/scripts/screenshots.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Set-Content $fakeUsb @(
$env:FASTBOOT_STUDIO_FAKE_USB = $fakeUsb
# No update check by itself while the screens are captured; the update dialog is tested at the end.
$env:FASTBOOT_STUDIO_UPDATE_FEED = 'off'
# No tour or What's new over the pages; both are captured on their own near the end.
$env:FASTBOOT_STUDIO_WELCOME = 'off'
Remove-Item $saveRoot -Recurse -Force -ErrorAction SilentlyContinue

function Save-Desktop([string] $name) {
Expand Down Expand Up @@ -1171,6 +1173,130 @@ Stop-Process -Id $proc.Id -Force
Remove-Item Env:FASTBOOT_STUDIO_THEME
Write-Host "captured the light theme"

# ---------------------------------------------------------------- the tour, What's new and the help panel
# The first start: four steps, the page picked on the map opens at the end, and its ؟ opens
# the help at the side. Then the start of a new version: What's new. In English, then Arabic.
function Tour([string] $prefix, [string] $okLabel) {
$env:FASTBOOT_STUDIO_WELCOME = 'on'
$env:FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER = 'missing'
Launch-App
$next = Wait-For { Shown $root 'welcome_next' } 30 "the tour ($prefix)"
Wait-For { Shown $root ('welcome_language_' + $(if ($prefix -eq 'ar') { 'ar' } else { 'en' })) } 10 "the language step ($prefix)" | Out-Null
Start-Sleep -Milliseconds 600
Save-Window "$prefix-welcome-1-language"
Press 'welcome_next'
Wait-For { Shown $root 'welcome_page_samsung' } 10 "the page map ($prefix)" | Out-Null
Press 'welcome_page_samsung'
Start-Sleep -Milliseconds 600
Save-Window "$prefix-welcome-2-pages"
Press 'welcome_next'
# The check reads the PC: the Samsung driver is "missing", the stand-in USB list has a phone without a driver.
Wait-For { Shown $root 'welcome_install_samsung' } 30 "the quick check ($prefix)" | Out-Null
Wait-For { Shown $root 'welcome_usb_check' } 30 "the USB line of the check ($prefix)" | Out-Null
Start-Sleep -Milliseconds 600
Save-Window "$prefix-welcome-3-check"
Press 'welcome_next'
Start-Sleep -Milliseconds 800
Save-Window "$prefix-welcome-4-safety"
Press 'welcome_next'
# The page picked on the map is open, and the tour is gone.
Wait-For { Shown $root 'samsung_open' } 10 "the Samsung page after the tour ($prefix)" | Out-Null
if ($null -ne (Shown $root 'welcome_next')) { Fail "the tour is still showing after Start ($prefix)" }
Press 'help_samsung'
Wait-For { Shown $root 'help_close' } 10 "the Samsung help ($prefix)" | Out-Null
Start-Sleep -Milliseconds 800
Save-Window "$prefix-help-samsung"
# Another page while the help is open: its own help follows.
Go 'device_tab' | Out-Null
Start-Sleep -Milliseconds 800
Save-Window "$prefix-help-device"
Press 'help_close'
Wait-For { $null -eq (Shown $root 'help_close') } 10 "the help to close ($prefix)" | Out-Null
if (Test-Path $crashLog) { Fail "the app recorded an unhandled exception in the tour ($prefix)" }
Stop-Process -Id $proc.Id -Force
Start-Sleep -Seconds 2

$env:FASTBOOT_STUDIO_WELCOME = 'whatsnew'
Launch-App
Wait-For { $n = Shown $root 'welcome_next'; $null -ne $n -and $n.Current.Name -eq $okLabel } 30 "What's new ($prefix)" | Out-Null
Start-Sleep -Milliseconds 600
Save-Window "$prefix-whats-new"
Press 'welcome_next'
Wait-For { $null -eq (Shown $root 'welcome_next') } 10 "What's new to close ($prefix)" | Out-Null
if (Test-Path $crashLog) { Fail "the app recorded an unhandled exception in What's new ($prefix)" }
Stop-Process -Id $proc.Id -Force
Remove-Item Env:FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER
$env:FASTBOOT_STUDIO_WELCOME = 'off'
Start-Sleep -Seconds 2
}
Tour 'en' 'OK'
if ($Arabic) {
$env:FASTBOOT_STUDIO_LANG = 'ar'
Tour 'ar' 'تمام'
Remove-Item Env:FASTBOOT_STUDIO_LANG
}
Write-Host "tour, What's new and help: shown, the picked page opened, the help followed the page"

# ---------------------------------------------------------------- Download mode (Odin, reading only)
# A stand-in phone answers the Odin protocol on a made-up COM9: the window finds it, reads its
# PIT and model, and restarts it. Then a port another program holds, which must fail clearly.
function Save-Dialog($dialog, [string] $name) {
[Win32]::SetForegroundWindow([IntPtr]$dialog.Current.NativeWindowHandle) | Out-Null
Start-Sleep -Milliseconds 800
$b = $dialog.Current.BoundingRectangle
$bmp = New-Object System.Drawing.Bitmap ([int]$b.Width), ([int]$b.Height)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen([int]$b.Left, [int]$b.Top, 0, 0, $bmp.Size)
$path = Join-Path $Out "$name.png"
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
$g.Dispose(); $bmp.Dispose()
Write-Host "saved $path ($([int]$b.Width)x$([int]$b.Height))"
}

function Odin([string] $prefix, [string] $mode) {
$env:FASTBOOT_STUDIO_FAKE_ODIN = $mode
$env:FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER = 'installed'
Launch-App
Go 'samsung_tab' | Out-Null
Press 'samsung_odin'
$dialog = Wait-For { Find-Dialog } 20 "the Download mode window ($prefix)"
$read = Wait-For { $b = By-Id $dialog 'odin_primary'; if ($null -ne $b -and $b.Current.IsEnabled) { $b } } 20 "the stand-in phone to be found ($prefix)"
Save-Dialog $dialog "$prefix-odin-1-ready"
$label = $read.Current.Name
$read.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
if ($mode -eq 'phone') {
Wait-For { Shown $dialog 'odin_extra' } 30 "the reading to finish ($prefix)" | Out-Null
Save-Dialog $dialog "$prefix-odin-2-read"
} else {
Wait-For { $b = By-Id $dialog 'odin_primary'; $null -ne $b -and $b.Current.Name -ne $label -and -not $b.Current.IsOffscreen } 30 "the failure ($prefix)" | Out-Null
Save-Dialog $dialog "$prefix-odin-3-$mode"
}
$close = By-Id $dialog 'odin_secondary'
$close.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
Wait-For { $null -eq (Find-Dialog) } 10 "the Download mode window to close ($prefix)" | Out-Null
Go 'logs_tab' | Out-Null
$logText = (By-Id $root 'log_text').GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern).Current.Value
if ($mode -eq 'phone') {
if ($logText -notmatch 'odin: handshake LOKE') { Fail "the Odin handshake was not logged ($prefix)" }
if ($logText -notmatch 'odin: PIT, \d+ bytes') { Fail "the PIT was not read ($prefix)" }
if ($logText -notmatch 'odin: restart Normal') { Fail "the phone was not restarted ($prefix)" }
} elseif ($logText -notmatch 'odin: reading failed') {
Fail "the failure was not logged ($prefix)"
}
if (Test-Path $crashLog) { Fail "the app recorded an unhandled exception in the Download mode window ($prefix)" }
Stop-Process -Id $proc.Id -Force
Remove-Item Env:FASTBOOT_STUDIO_FAKE_ODIN, Env:FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER
Start-Sleep -Seconds 2
}
Odin 'en' 'phone'
Odin 'en' 'busy'
if ($Arabic) {
$env:FASTBOOT_STUDIO_LANG = 'ar'
Odin 'ar' 'phone'
Remove-Item Env:FASTBOOT_STUDIO_LANG
}
Write-Host "Download mode: the stand-in phone found, read and restarted; a busy port reported"

# ---------------------------------------------------------------- the update dialog
# A test feed stands in for GitHub: a newer release whose zip is a local file. The dialog must
# open by itself, download the zip, keep it only when its SHA-256 matches, and delete it otherwise.
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,51 @@ jobs:
if (-not (Test-Path (Join-Path publish $f))) { throw "$f missing from publish output" }
}

# The Samsung driver button keeps an installer only when Windows verifies Samsung's
# signature on it. Checked here with Samsung's real file; if Samsung cannot be reached,
# this only warns, since the check is about the app, not Samsung's servers.
- name: Samsung driver link and signature
shell: pwsh
run: |
$gen = 'tools/FastbootEnhance.SampleGen'
$neg = dotnet run --project $gen -c Release -- signer publish/adb.exe
Write-Host "adb.exe: $neg"
if (-not ($neg -match 'samsung=False')) { throw "adb.exe was taken for Samsung's driver" }
# A working check names Google on adb.exe; a broken one would name nobody.
if (-not ($neg -match 'O=Google LLC')) { throw "adb.exe's Google signature was not verified" }
$copy = Join-Path $env:RUNNER_TEMP 'locked-adb.exe'
Copy-Item publish/adb.exe $copy
Copy-Item publish/AdbWinApi.dll, publish/AdbWinUsbApi.dll $env:RUNNER_TEMP
$run = dotnet run --project $gen -c Release -- run-locked $copy version
Write-Host "run while held open: $run"
if (-not ($run -match 'exit=0')) { throw "a program held open for its check could not be started" }
$bad = Join-Path $env:RUNNER_TEMP 'unsigned.exe'
Copy-Item publish/adb.exe $bad
$bytes = [IO.File]::ReadAllBytes($bad); $bytes[1024] = $bytes[1024] -bxor 0xFF; [IO.File]::WriteAllBytes($bad, $bytes)
$tampered = dotnet run --project $gen -c Release -- signer $bad
Write-Host "tampered adb.exe: $tampered"
if (-not ($tampered -match 'no trusted signature')) { throw "a tampered file passed the signature check" }
try {
$page = Join-Path $env:RUNNER_TEMP 'samsung.html'
Invoke-WebRequest 'https://developer.samsung.com/android-usb-driver' -UseBasicParsing -TimeoutSec 60 -OutFile $page
$link = dotnet run --project $gen -c Release -- samsung-link $page
if ($LASTEXITCODE -ne 0) { Write-Warning "no download link found on Samsung's page"; $link = 'https://developer.samsung.com/SDP/file/a2fcaf64-7aa3-49f7-b06f-9e646590d528' }
Write-Host "link: $link"
$drv = Join-Path $env:RUNNER_TEMP 'samsung-driver.exe'
Invoke-WebRequest $link -UseBasicParsing -TimeoutSec 300 -OutFile $drv
} catch {
Write-Warning "Samsung could not be reached: $_"
exit 0
}
$head = [IO.File]::ReadAllBytes($drv)
if ($head.Length -lt 5MB -or $head[0] -ne 0x4D -or $head[1] -ne 0x5A) {
Write-Warning "Samsung did not send the installer (got $($head.Length) bytes)"
exit 0
}
$pos = dotnet run --project $gen -c Release -- signer $drv
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
with:
name: FastbootStudio-win-x64
Expand Down
Binary file added Assets/appname-dark.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 added Assets/appname-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 added Assets/signature-dark.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 added Assets/signature-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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. -->

## v2.8.0
- زر ؟ بكل صفحة: شرح وخطوات وتحذيرات بلغتك
- جولة ترحيب، و«الجديد» يطلع بعد كل تحديث
- تعريف سامسونج: يتحمّل من سامسونج ويتفحص توقيعه ويتثبّت
- ينبهك إذا boot مروّت، ويقترح تجرب الريكفري مؤقتاً أول
- سامسونج: حالة Knox، وخطوات Magisk أوضح (CSC أول مرة، HOME_CSC للتحديث)
- جديد: قراءة الجوال في وضع التحميل (أودين): الموديل والمنطقة وجدول PIT

**English**
- A ? on every page: help, steps and dangers in your language
- A welcome tour, and What's new after each update
- Samsung driver: downloaded from Samsung, signature checked, installed
- A rooted boot image is named; a recovery suggests Boot once first
- Samsung: Knox status; clearer Magisk steps (CSC first, HOME_CSC to update)
- New: read a phone in Download mode (Odin): model, region and PIT

## v2.7.1
- القوايم ما تبيّض وقت فحص MD5 أو الاستخراج، تبقى داكنة لين يخلص الشغل

Expand Down
70 changes: 70 additions & 0 deletions DriverUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using FastbootEnhance.Core.Usb;
using Microsoft.Win32;
using System;
using System.IO;
using System.Linq;

namespace FastbootEnhance
{
/// <summary>
/// Samsung's USB driver: whether it is installed, and the window that downloads it from
/// Samsung, checks Samsung signed it and runs its installer. Download (Odin) mode needs it;
/// it is the same driver Odin itself uses, so installing it breaks nothing.
/// FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER ("installed" or "missing") answers for tests.
/// </summary>
static class DriverUI
{
const string StandInVariable = "FASTBOOT_STUDIO_FAKE_SAMSUNG_DRIVER";

/// <summary>True when Windows' driver store holds the Download-mode driver, or the installer is listed in Programs.</summary>
public static bool SamsungInstalled()
{
string standIn = Environment.GetEnvironmentVariable(StandInVariable);
if (!string.IsNullOrEmpty(standIn))
return string.Equals(standIn.Trim(), "installed", StringComparison.OrdinalIgnoreCase);

try
{
string store = Path.Combine(Environment.SystemDirectory, "DriverStore", "FileRepository");
if (Directory.Exists(store) && Directory.EnumerateDirectories(store)
.Any(d => SamsungDriver.IsDriverStoreFolder(Path.GetFileName(d))))
return true;
}
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException)
{
}

const string uninstall = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
foreach (RegistryView view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
{
try
{
using (RegistryKey root = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (RegistryKey list = root.OpenSubKey(uninstall))
{
if (list == null)
continue;
foreach (string name in list.GetSubKeyNames())
{
using (RegistryKey entry = list.OpenSubKey(name))
{
if (SamsungDriver.IsProgramName(entry?.GetValue("DisplayName") as string))
return true;
}
}
}
}
catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException)
{
}
}
return false;
}

/// <summary>Opens the download-and-install window; <paramref name="after"/> runs when it closes.</summary>
public static void InstallSamsung(Action after = null)
{
DriverWindow.Show(after);
}
}
}
50 changes: 50 additions & 0 deletions DriverWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<Window x:Class="FastbootEnhance.DriverWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:props="clr-namespace:FastbootEnhance.Properties"
WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True" Background="Transparent"
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.Effect>
<DropShadowEffect BlurRadius="26" ShadowDepth="6" Opacity="0.55" Color="#000000"/>
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Border x:Name="strip" Height="3" VerticalAlignment="Top" CornerRadius="11,11,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"
Margin="0,0,14,0" VerticalAlignment="Top"
Background="{StaticResource AccentSoft}" BorderBrush="{StaticResource AccentLine}">
<Path FlowDirection="LeftToRight" x:Name="glyph" Width="18" Height="18" Stretch="Uniform" StrokeThickness="2"
StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Stroke="{StaticResource Accent}"/>
</Border>
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="heading" Style="{StaticResource CardTitle}" Margin="0" FontSize="15" TextWrapping="Wrap"/>
<TextBlock x:Name="subheading" Foreground="{StaticResource Dim}" FontSize="12" Margin="0,2,0,0" TextWrapping="Wrap"/>
</StackPanel>
</DockPanel>

<StackPanel Grid.Row="1" Margin="70,12,22,0">
<ProgressBar x:Name="progress" Height="6" Margin="0,4,0,6" Visibility="Collapsed" Maximum="100"/>
<TextBlock x:Name="message" TextWrapping="Wrap" FontSize="12.5" LineHeight="19"/>
</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">
<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"/>
</StackPanel>
</Border>
</Grid>
</Border>
</Window>
Loading
Loading