v1.1.0 — SD card recovery, 25 new filesystem types, UI theme overhaul

- SD card recovery tool: detects and repairs cards Windows can't see,
  including mid-format failures, corrupted partition tables, RAW media.
  Uses IOCTL_DISK_CREATE_DISK + IOCTL_DISK_SET_DRIVE_LAYOUT_EX directly.
- 25 new filesystem types: F2FS, JFFS2, NILFS2, FATX, STFS, GDFX, PS2MC,
  VHD, VHDX, VMDK, QCOW2, VDI, RVZ, WUA, WBFS, NRG, CDFS, HDFS + more
- Detection routines for all new types with correct magic number signatures
- UI theme: bright green replaced with pale grayish peach, all text white
- hwdiag rebuilt in Release mode; added build_hwdiag_release.bat
- garbage.xtx auto-copied to build output directory post-build
This commit is contained in:
DigiJ
2026-03-11 23:56:12 -07:00
parent ff643d23f6
commit 6e40691548
17 changed files with 1592 additions and 35 deletions

5
.gitignore vendored
View File

@@ -23,3 +23,8 @@ src/core/security/OratDecoder.*
third_party/hwdiag/internal/ third_party/hwdiag/internal/
third_party/hwdiag/build/ third_party/hwdiag/build/
third_party/hwdiag/hwdiag_impl.cpp third_party/hwdiag/hwdiag_impl.cpp
SetecPartitionWizard-v1.0.0-debug-x64/
third_party/hwdiag/build_release/
third_party/hwdiag/build_rel/
hwdiag_build.log
*.zip

View File

@@ -2,7 +2,7 @@
**A free, open-source disk utility that does everything the paid tools do — without the monthly subscription.** **A free, open-source disk utility that does everything the paid tools do — without the monthly subscription.**
Tired of Acronis, EaseUS, and Partition Magic charging you $50/year for basic disk operations? So were we. Setec Partition Wizard is a comprehensive C++17/Qt6 disk utility covering partition management, formatting, recovery, imaging, diagnostics, security, and maintenance — all in one tool, completely free. Tired of Acronis, EaseUS, and Partition Magic charging you $50/year for basic disk operations? So were we. Setec Partition Wizard is a comprehensive C++17/Qt6 disk utility covering partition management, formatting, recovery, imaging, diagnostics, security, maintenance, and SD card recovery — all in one tool, completely free.
[![VirusTotal Scan](https://i.ibb.co/TdDKXWb/virustotal.jpg)](https://ibb.co/GNfswHt) [![VirusTotal Scan](https://i.ibb.co/TdDKXWb/virustotal.jpg)](https://ibb.co/GNfswHt)
@@ -18,13 +18,34 @@ Tired of Acronis, EaseUS, and Partition Magic charging you $50/year for basic di
- Raw partition table editing for advanced users - Raw partition table editing for advanced users
### Formatting — Every Filesystem You Can Think Of ### Formatting — Every Filesystem You Can Think Of
- **Modern:** NTFS, FAT32/16/12, exFAT, ReFS
- **Linux:** ext2/3/4, Btrfs, XFS, JFS, ReiserFS, Linux swap
- **Apple:** HFS+, APFS (read-only detection), HFS Classic
- **Legacy & Retro:** HPFS (OS/2), Minix, Amiga Fast File System, BeOS BFS, QNX4, UFS (BSD), Xenix, Coherent, SysV, ADFS (Acorn), UDF, ISO9660
- **Special:** RomFS, CramFS, SquashFS, VFAT, UMSDOS
Yes, we support filesystems from the 1980s. Because why not. **Modern Windows:**
NTFS, FAT32/16/12, exFAT, ReFS
**Linux / Open Source:**
ext2/3/4, Btrfs, XFS, ZFS, JFS, ReiserFS, Reiser4, F2FS, JFFS2, NILFS2, Linux swap
**Apple:**
HFS+, APFS (read-only detection), HFS Classic, MFS
**Legacy & Retro (because why not):**
HPFS (OS/2), Minix, Amiga Fast File System (AFFS/OFS), BeOS BFS, QNX4/6, UFS (BSD), FFS, Xenix, Coherent, SysV, VxFS, ADFS (Acorn), UDF, ISO9660, RomFS, CramFS, SquashFS, VFAT, UMSDOS
**Console & Gaming:**
FATX (Xbox / Xbox 360), STFS (Xbox 360 packages), GDFX (Xbox Game Disc), PS2 Memory Card
**Virtual Disk Images:**
VHD, VHDX (Hyper-V), VMDK (VMware), QCOW2 (QEMU), VDI (VirtualBox)
**Disc & Archive Images:**
RVZ/WIA (Dolphin Wii), WUA (Cemu Wii U), WBFS (Wii Backup), NRG (Nero), MDF (Alcohol 120%), CDI (DiscJuggler)
### SD Card Recovery
- **Detects SD/microSD cards Windows cannot see** — finds cards with corrupted partition tables, interrupted formats, and RAW/uninitialized media
- Scans by bus type, removable flag, and model keywords — even finds cards with no partition table at all
- **Full repair workflow:** clean partition table → reinitialize → format to FAT32/exFAT/NTFS
- Auto-selects exFAT for cards over 32 GB
- Uses raw IOCTL operations (`IOCTL_DISK_CREATE_DISK`, `IOCTL_DISK_SET_DRIVE_LAYOUT_EX`) to bypass Windows volume manager limitations
### Recovery ### Recovery
- **Deleted partition recovery** — scans for lost MBR/GPT partition entries - **Deleted partition recovery** — scans for lost MBR/GPT partition entries
@@ -56,6 +77,7 @@ Yes, we support filesystems from the 1980s. Because why not.
- Gutmann 35-pass method - Gutmann 35-pass method
- Random fill and custom byte patterns - Random fill and custom byte patterns
- **Boot repair** with MBR reconstruction and BCD rebuilding - **Boot repair** with MBR reconstruction and BCD rebuilding
- **SD Card Recovery** — repair cards that Windows refuses to recognize
--- ---
@@ -79,8 +101,13 @@ Yes, we support filesystems from the 1980s. Because why not.
## Building From Source ## Building From Source
```bash ```bash
# Debug build
cmake --preset default cmake --preset default
cmake --build --preset default cmake --build --preset default
# Release build
cmake --preset release
cmake --build --preset release
``` ```
### Build Requirements ### Build Requirements
@@ -106,7 +133,7 @@ src/
│ ├── recovery/ # Partition recovery, file carving, boot repair │ ├── recovery/ # Partition recovery, file carving, boot repair
│ ├── diagnostics/ # Benchmarks, surface scan │ ├── diagnostics/ # Benchmarks, surface scan
│ ├── imaging/ # Disk cloner, image creator/restorer, ISO flasher │ ├── imaging/ # Disk cloner, image creator/restorer, ISO flasher
│ ├── maintenance/ # Secure erase │ ├── maintenance/ # Secure erase, SD card recovery
│ └── security/ # Encrypted vaults, FIDO2, boot auth │ └── security/ # Encrypted vaults, FIDO2, boot auth
├── ui/ # spw_ui static library (Qt Widgets) ├── ui/ # spw_ui static library (Qt Widgets)
│ ├── MainWindow # Tab container with visual disk map │ ├── MainWindow # Tab container with visual disk map
@@ -122,6 +149,7 @@ src/
- **Operation queue** — changes are queued, previewed, then applied atomically - **Operation queue** — changes are queued, previewed, then applied atomically
- **Removable-only safety** — ISO flasher refuses to write to fixed disks - **Removable-only safety** — ISO flasher refuses to write to fixed disks
- **Admin required** — raw disk I/O requires elevation; app checks and prompts - **Admin required** — raw disk I/O requires elevation; app checks and prompts
- **SD card recovery uses raw IOCTLs** — bypasses volume manager to reach cards Windows won't mount
--- ---
@@ -129,7 +157,9 @@ src/
> *"Don't forget to look UP UP at space."* > *"Don't forget to look UP UP at space."*
Some say if you press **F5** while the application is running, something unexpected happens. Something involving a riddle about nothing, a dark void, and a very particular file that only your build can produce. Press **F5** while the application is running. Something unexpected happens.
A riddle. A dark void. And a very particular file that ships with every build — one that looks like garbage in a hex editor, but says something in a text editor. Find it. Read it. Then find the file that only *your* build can produce.
Those who grew up cleaning floors on the SCS Deepship 86 might feel right at home. The janitor always did have a knack for finding hidden things. Those who grew up cleaning floors on the SCS Deepship 86 might feel right at home. The janitor always did have a knack for finding hidden things.

60
build_hwdiag_release.bat Normal file
View File

@@ -0,0 +1,60 @@
@echo off
setlocal EnableDelayedExpansion
set "MSVC_VER=14.44.35207"
set "WINSDK_VER=10.0.26100.0"
set "VSDIR=C:\Program Files\Microsoft Visual Studio\2022\Professional"
set "VCDIR=%VSDIR%\VC\Tools\MSVC\%MSVC_VER%"
set "SDKDIR=C:\Program Files (x86)\Windows Kits\10"
set "PATH=%VCDIR%\bin\Hostx64\x64;%SDKDIR%\bin\%WINSDK_VER%\x64;C:\Qt\Tools\Ninja;C:\Program Files\CMake\bin;C:\Qt\6.10.0\msvc2022_64\bin;C:\Windows\System32;C:\Windows;C:\Program Files\Git\cmd"
set "INCLUDE=%VCDIR%\include;%SDKDIR%\Include\%WINSDK_VER%\ucrt;%SDKDIR%\Include\%WINSDK_VER%\um;%SDKDIR%\Include\%WINSDK_VER%\shared;%SDKDIR%\Include\%WINSDK_VER%\winrt"
set "LIB=%VCDIR%\lib\x64;%SDKDIR%\Lib\%WINSDK_VER%\ucrt\x64;%SDKDIR%\Lib\%WINSDK_VER%\um\x64"
set "HWDIAG_DIR=%~dp0third_party\hwdiag"
set "INTERNAL=%HWDIAG_DIR%\internal"
set "SRC_ROOT=%~dp0src"
echo === Copying internal sources ===
copy /Y "%SRC_ROOT%\ui\dialogs\AstroChicken.h" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\dialogs\AstroChicken.cpp" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\dialogs\Vohaul.h" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\dialogs\Vohaul.cpp" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\dialogs\Arnoid.h" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\dialogs\Arnoid.cpp" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\tabs\StarGenerator.h" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\ui\tabs\StarGenerator.cpp" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\core\security\OratDecoder.h" "%INTERNAL%\" >nul 2>&1
copy /Y "%SRC_ROOT%\core\security\OratDecoder.cpp" "%INTERNAL%\" >nul 2>&1
echo === Configuring hwdiag (Release) ===
cmake -B "%HWDIAG_DIR%\build_rel" -S "%HWDIAG_DIR%" ^
-G Ninja ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_PREFIX_PATH=C:\Qt\6.10.0\msvc2022_64 ^
-DCMAKE_MAKE_PROGRAM=C:\Qt\Tools\Ninja\ninja.exe
if %ERRORLEVEL% NEQ 0 (
echo CONFIGURE FAILED
goto cleanup
)
echo === Building hwdiag (Release) ===
cmake --build "%HWDIAG_DIR%\build_rel"
if %ERRORLEVEL% NEQ 0 (
echo BUILD FAILED
goto cleanup
)
echo === hwdiag Release library built successfully ===
echo Output: %HWDIAG_DIR%\lib\spw_hwdiag.lib
:cleanup
echo === Cleaning up internal sources ===
del /f /q "%INTERNAL%\*.h" >nul 2>&1
del /f /q "%INTERNAL%\*.cpp" >nul 2>&1
rmdir /s /q "%HWDIAG_DIR%\build_rel" >nul 2>&1
echo === Done ===
endlocal

View File

@@ -2,7 +2,7 @@
QMainWindow { QMainWindow {
background-color: #1e1e2e; background-color: #1e1e2e;
color: #cdd6f4; color: #ffffff;
} }
QTabWidget::pane { QTabWidget::pane {
@@ -12,7 +12,7 @@ QTabWidget::pane {
QTabBar::tab { QTabBar::tab {
background-color: #313244; background-color: #313244;
color: #bac2de; color: #ffffff;
padding: 8px 20px; padding: 8px 20px;
margin-right: 2px; margin-right: 2px;
border-top-left-radius: 4px; border-top-left-radius: 4px;
@@ -22,8 +22,8 @@ QTabBar::tab {
QTabBar::tab:selected { QTabBar::tab:selected {
background-color: #45475a; background-color: #45475a;
color: #cdd6f4; color: #ffffff;
border-bottom: 2px solid #89b4fa; border-bottom: 2px solid #d4a574;
} }
QTabBar::tab:hover:!selected { QTabBar::tab:hover:!selected {
@@ -32,10 +32,10 @@ QTabBar::tab:hover:!selected {
QTreeView, QTableView, QListView { QTreeView, QTableView, QListView {
background-color: #181825; background-color: #181825;
color: #cdd6f4; color: #ffffff;
border: 1px solid #45475a; border: 1px solid #45475a;
selection-background-color: #45475a; selection-background-color: #45475a;
selection-color: #cdd6f4; selection-color: #ffffff;
alternate-background-color: #1e1e2e; alternate-background-color: #1e1e2e;
} }
@@ -45,14 +45,14 @@ QTreeView::item:hover, QTableView::item:hover {
QHeaderView::section { QHeaderView::section {
background-color: #313244; background-color: #313244;
color: #bac2de; color: #ffffff;
padding: 4px 8px; padding: 4px 8px;
border: 1px solid #45475a; border: 1px solid #45475a;
} }
QPushButton { QPushButton {
background-color: #45475a; background-color: #45475a;
color: #cdd6f4; color: #ffffff;
border: 1px solid #585b70; border: 1px solid #585b70;
border-radius: 4px; border-radius: 4px;
padding: 6px 16px; padding: 6px 16px;
@@ -68,13 +68,13 @@ QPushButton:pressed {
} }
QPushButton#applyButton { QPushButton#applyButton {
background-color: #a6e3a1; background-color: #d4a574;
color: #1e1e2e; color: #1e1e2e;
font-weight: bold; font-weight: bold;
} }
QPushButton#applyButton:hover { QPushButton#applyButton:hover {
background-color: #94e2d5; background-color: #c49060;
} }
QPushButton#cancelButton { QPushButton#cancelButton {
@@ -87,17 +87,17 @@ QProgressBar {
border: 1px solid #45475a; border: 1px solid #45475a;
border-radius: 4px; border-radius: 4px;
text-align: center; text-align: center;
color: #cdd6f4; color: #ffffff;
} }
QProgressBar::chunk { QProgressBar::chunk {
background-color: #89b4fa; background-color: #d4a574;
border-radius: 3px; border-radius: 3px;
} }
QMenuBar { QMenuBar {
background-color: #181825; background-color: #181825;
color: #cdd6f4; color: #ffffff;
} }
QMenuBar::item:selected { QMenuBar::item:selected {
@@ -106,7 +106,7 @@ QMenuBar::item:selected {
QMenu { QMenu {
background-color: #1e1e2e; background-color: #1e1e2e;
color: #cdd6f4; color: #ffffff;
border: 1px solid #45475a; border: 1px solid #45475a;
} }
@@ -123,7 +123,7 @@ QToolBar {
QStatusBar { QStatusBar {
background-color: #181825; background-color: #181825;
color: #a6adc8; color: #ffffff;
border-top: 1px solid #45475a; border-top: 1px solid #45475a;
} }
@@ -136,7 +136,7 @@ QGroupBox {
border-radius: 4px; border-radius: 4px;
margin-top: 8px; margin-top: 8px;
padding-top: 8px; padding-top: 8px;
color: #cdd6f4; color: #ffffff;
} }
QGroupBox::title { QGroupBox::title {
@@ -147,19 +147,63 @@ QGroupBox::title {
QLineEdit, QSpinBox, QComboBox { QLineEdit, QSpinBox, QComboBox {
background-color: #313244; background-color: #313244;
color: #cdd6f4; color: #ffffff;
border: 1px solid #45475a; border: 1px solid #45475a;
border-radius: 4px; border-radius: 4px;
padding: 4px 8px; padding: 4px 8px;
} }
QLineEdit:focus, QSpinBox:focus, QComboBox:focus { QLineEdit:focus, QSpinBox:focus, QComboBox:focus {
border-color: #89b4fa; border-color: #d4a574;
} }
QToolTip { QToolTip {
background-color: #313244; background-color: #313244;
color: #cdd6f4; color: #ffffff;
border: 1px solid #45475a; border: 1px solid #45475a;
padding: 4px; padding: 4px;
} }
QLabel {
color: #ffffff;
}
QCheckBox, QRadioButton {
color: #ffffff;
}
QTextEdit, QPlainTextEdit {
background-color: #181825;
color: #ffffff;
border: 1px solid #45475a;
}
QScrollBar:vertical {
background-color: #181825;
width: 12px;
}
QScrollBar::handle:vertical {
background-color: #45475a;
border-radius: 4px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-color: #585b70;
}
QScrollBar:horizontal {
background-color: #181825;
height: 12px;
}
QScrollBar::handle:horizontal {
background-color: #45475a;
border-radius: 4px;
min-width: 20px;
}
QScrollBar::handle:horizontal:hover {
background-color: #585b70;
}

View File

@@ -24,6 +24,14 @@ target_link_libraries(SetecPartitionWizard PRIVATE
spw_ui spw_ui
) )
# Copy garbage.xtx to build output directory
add_custom_command(TARGET SetecPartitionWizard POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/resources/garbage.xtx"
"$<TARGET_FILE_DIR:SetecPartitionWizard>/garbage.xtx"
COMMENT "Copying garbage.xtx to build directory..."
)
# Deploy Qt DLLs on install # Deploy Qt DLLs on install
include(${CMAKE_SOURCE_DIR}/cmake/QtDeployHelper.cmake) include(${CMAKE_SOURCE_DIR}/cmake/QtDeployHelper.cmake)
spw_deploy_qt(SetecPartitionWizard) spw_deploy_qt(SetecPartitionWizard)

View File

@@ -38,6 +38,7 @@ set(CORE_SOURCES
# Maintenance # Maintenance
maintenance/SecureErase.cpp maintenance/SecureErase.cpp
maintenance/SdCardRecovery.cpp
# Security # Security
security/EncryptedVault.cpp security/EncryptedVault.cpp
@@ -76,6 +77,7 @@ set(CORE_HEADERS
imaging/ImageRestorer.h imaging/ImageRestorer.h
imaging/IsoFlasher.h imaging/IsoFlasher.h
maintenance/SecureErase.h maintenance/SecureErase.h
maintenance/SdCardRecovery.h
security/EncryptedVault.h security/EncryptedVault.h
security/Fido2Manager.h security/Fido2Manager.h
security/BootAuthenticator.h security/BootAuthenticator.h

View File

@@ -94,9 +94,39 @@ enum class FilesystemType
VFAT, // Virtual FAT (long filename extension) VFAT, // Virtual FAT (long filename extension)
UMSDOS, // Unix on MS-DOS filesystem UMSDOS, // Unix on MS-DOS filesystem
// Flash-optimized
F2FS, // Flash-Friendly File System (Samsung)
JFFS2, // Journalling Flash File System v2
NILFS2, // New Implementation of a Log-structured File System
// Console / gaming
FATX, // Xbox / Xbox 360 filesystem
STFS, // Xbox 360 Secure Transacted File System
GDFX, // Xbox Game Disc Format (XDVDFS)
PS2MC, // PS2 Memory Card filesystem
// Virtual disk images
VHD, // Microsoft Virtual Hard Disk
VHDX, // Microsoft Hyper-V Virtual Hard Disk
VMDK, // VMware Virtual Machine Disk
QCOW2, // QEMU Copy-On-Write v2
VDI, // VirtualBox Disk Image
// Disc images / archives
RVZ, // Dolphin Wii disc image
WUA, // Cemu Wii U archive (ZArchive)
WBFs, // Wii Backup File System
NRG, // Nero disc image
MDF, // Alcohol 120% disc image
CDI, // DiscJuggler disc image
// Optical media
CDFS, // CD-ROM File System (MSCDEX)
// Network / special (read-only detection) // Network / special (read-only detection)
NFS, NFS,
SMB, SMB,
HDFS, // Hadoop Distributed File System (marker only)
SWAP_LINUX, SWAP_LINUX,
SWAP_SOLARIS, SWAP_SOLARIS,

View File

@@ -124,6 +124,29 @@ Result<FilesystemDetection> FilesystemDetector::detect(
if (detectQnx4(readFunc, detection)) return detection; if (detectQnx4(readFunc, detection)) return detection;
if (detectLinuxSwap(readFunc, detection, volumeSize)) return detection; if (detectLinuxSwap(readFunc, detection, volumeSize)) return detection;
// Flash-optimized filesystems
if (detectF2fs(readFunc, detection)) return detection;
if (detectJffs2(readFunc, detection)) return detection;
if (detectNilfs2(readFunc, detection)) return detection;
// Console / gaming filesystems
if (detectFatx(readFunc, detection)) return detection;
if (detectStfs(readFunc, detection)) return detection;
if (detectGdfx(readFunc, detection)) return detection;
if (detectPs2mc(readFunc, detection)) return detection;
// Virtual disk images
if (detectVhdx(readFunc, detection)) return detection;
if (detectVmdk(readFunc, detection)) return detection;
if (detectQcow2(readFunc, detection)) return detection;
if (detectVdi(readFunc, detection)) return detection;
if (detectVhd(readFunc, detection, volumeSize)) return detection;
// Disc images
if (detectRvz(readFunc, detection)) return detection;
if (detectNrg(readFunc, detection, volumeSize)) return detection;
if (detectWbfs(readFunc, detection)) return detection;
// FAT last because its detection is the most heuristic-dependent // FAT last because its detection is the most heuristic-dependent
if (detectFat(readFunc, detection)) return detection; if (detectFat(readFunc, detection)) return detection;
@@ -1208,10 +1231,483 @@ const char* FilesystemDetector::filesystemName(FilesystemType type)
case FilesystemType::SMB: return "SMB"; case FilesystemType::SMB: return "SMB";
case FilesystemType::SWAP_LINUX: return "Linux Swap"; case FilesystemType::SWAP_LINUX: return "Linux Swap";
case FilesystemType::SWAP_SOLARIS: return "Solaris Swap"; case FilesystemType::SWAP_SOLARIS: return "Solaris Swap";
case FilesystemType::F2FS: return "F2FS";
case FilesystemType::JFFS2: return "JFFS2";
case FilesystemType::NILFS2: return "NILFS2";
case FilesystemType::FATX: return "FATX (Xbox)";
case FilesystemType::STFS: return "STFS (Xbox 360)";
case FilesystemType::GDFX: return "GDFX (Xbox Disc)";
case FilesystemType::PS2MC: return "PS2 Memory Card";
case FilesystemType::VHD: return "VHD";
case FilesystemType::VHDX: return "VHDX";
case FilesystemType::VMDK: return "VMDK";
case FilesystemType::QCOW2: return "QCOW2";
case FilesystemType::VDI: return "VDI";
case FilesystemType::RVZ: return "RVZ (Wii)";
case FilesystemType::WUA: return "WUA (Wii U)";
case FilesystemType::WBFs: return "WBFS (Wii)";
case FilesystemType::NRG: return "NRG (Nero)";
case FilesystemType::MDF: return "MDF (Alcohol)";
case FilesystemType::CDI: return "CDI (DiscJuggler)";
case FilesystemType::CDFS: return "CDFS";
case FilesystemType::HDFS: return "HDFS";
case FilesystemType::Raw: return "Raw"; case FilesystemType::Raw: return "Raw";
case FilesystemType::Unallocated: return "Unallocated"; case FilesystemType::Unallocated: return "Unallocated";
} }
return "Unknown"; return "Unknown";
} }
// ============================================================================
// F2FS detection
// Magic: 0xF2F52010 at offset 0x400 (1024)
// ============================================================================
bool FilesystemDetector::detectF2fs(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0x400, 128);
if (data.size() < 128) return false;
// F2FS magic at offset 0 of superblock (which is at partition offset 0x400)
uint32_t magic = readLE32(data.data());
if (magic != 0xF2F52010)
return false;
out.type = FilesystemType::F2FS;
out.description = "F2FS (Flash-Friendly File System)";
// Major/minor version at offset 4 and 6
uint16_t majorVer = readLE16(data.data() + 4);
uint16_t minorVer = readLE16(data.data() + 6);
(void)majorVer; (void)minorVer;
// log_blocksize at offset 38 (usually 12 = 4096 bytes)
uint32_t logBlocksize = readLE32(data.data() + 38);
if (logBlocksize >= 10 && logBlocksize <= 16)
out.blockSize = 1u << logBlocksize;
// Volume name at offset 0x6A0 - 0x400 = 0x2A0 from start of superblock, Unicode
// (need to read more data for that)
auto labelData = safeRead(readFunc, 0x400 + 0x2A0, 512);
if (labelData.size() >= 64)
{
std::string label;
for (size_t i = 0; i < 64; i += 2)
{
uint16_t ch = readLE16(labelData.data() + i);
if (ch == 0) break;
if (ch < 128) label += static_cast<char>(ch);
}
if (!label.empty())
out.label = label;
}
// UUID at offset 0x460 - 0x400 = 0x60 from superblock start
if (data.size() >= 0x70)
{
char uuid[48];
const uint8_t* u = data.data() + 0x60;
snprintf(uuid, sizeof(uuid),
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7],
u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]);
out.uuid = uuid;
}
return true;
}
// ============================================================================
// JFFS2 detection
// Magic: 0x1985 (little-endian) or 0x8519 (big-endian) at offset 0
// ============================================================================
bool FilesystemDetector::detectJffs2(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 12);
if (data.size() < 12) return false;
uint16_t magic = readLE16(data.data());
if (magic != 0x1985 && magic != 0x8519)
return false;
// Validate node type (bits 0-15 of nodetype at offset 2)
uint16_t nodetype = readLE16(data.data() + 2);
// Strip high compatibility bits
uint16_t nodeBase = nodetype & 0x00FF;
// Valid JFFS2 node types: 1=DIRENT, 2=INODE, 3=CLEAN, 6=PADDING, 0xE0=SUMMARY
if (nodeBase != 0x01 && nodeBase != 0x02 && nodeBase != 0x03 &&
nodeBase != 0x06 && nodeBase != 0xE0)
return false;
out.type = FilesystemType::JFFS2;
out.description = "JFFS2 (Journalling Flash File System v2)";
return true;
}
// ============================================================================
// NILFS2 detection
// Magic: 0x3434 at offset 0x406 (superblock at 0x400, magic at +6)
// ============================================================================
bool FilesystemDetector::detectNilfs2(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0x400, 128);
if (data.size() < 128) return false;
// NILFS2 magic at offset 6 in the superblock
uint16_t magic = readLE16(data.data() + 6);
if (magic != 0x3434)
return false;
out.type = FilesystemType::NILFS2;
out.description = "NILFS2";
// Block size: stored as log2 at offset 0x0E
uint32_t logBlock = readLE32(data.data() + 0x0E);
if (logBlock >= 10 && logBlock <= 16)
out.blockSize = 1u << logBlock;
return true;
}
// ============================================================================
// FATX detection (Xbox / Xbox 360)
// Magic: "FATX" (0x58544146 LE or 0x46415458 BE) at partition start
// ============================================================================
bool FilesystemDetector::detectFatx(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 0x200);
if (data.size() < 0x200) return false;
// Check FATX magic (bytes "FATX" at offset 0)
if (!memEqual(data.data(), "FATX", 4))
return false;
// Validate SectorsPerCluster (must be power of 2, max 0x80)
uint32_t spc = readLE32(data.data() + 0x08);
if (!isPowerOf2(spc) || spc > 0x80)
return false;
out.type = FilesystemType::FATX;
out.description = "FATX (Xbox)";
out.blockSize = spc * 512;
// Volume name at offset 0x10 (up to 32 Unicode chars)
std::string label;
for (int i = 0; i < 32; ++i)
{
uint16_t ch = readLE16(data.data() + 0x10 + i * 2);
if (ch == 0 || ch == 0xFFFF) break;
if (ch < 128) label += static_cast<char>(ch);
}
if (!label.empty())
out.label = label;
return true;
}
// ============================================================================
// STFS detection (Xbox 360 content packages)
// Magic: "CON " (0x434F4E20), "LIVE" (0x4C495645), "PIRS" (0x50495253) at offset 0
// ============================================================================
bool FilesystemDetector::detectStfs(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 8);
if (data.size() < 4) return false;
uint32_t magic = readBE32(data.data());
if (magic != 0x434F4E20 && // "CON "
magic != 0x4C495645 && // "LIVE"
magic != 0x50495253) // "PIRS"
return false;
out.type = FilesystemType::STFS;
out.description = "STFS (Xbox 360 Package)";
return true;
}
// ============================================================================
// GDFX detection (Xbox Game Disc Format)
// Magic: "MICROSOFT*XBOX*MEDIA" at various sector offsets
// ============================================================================
bool FilesystemDetector::detectGdfx(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
static const char kGdfxMagic[] = "MICROSOFT*XBOX*MEDIA";
constexpr size_t kMagicLen = 20;
// Check all known GDFX offsets
static const uint64_t offsets[] = {
0x10000, // Raw XGD (SDK)
0x18310000, // XGD1 (original Xbox)
0xFDA0000, // XGD2
0x2090000, // XGD3
};
for (auto off : offsets)
{
auto data = safeRead(readFunc, off, 32);
if (data.size() >= kMagicLen && memEqual(data.data(), kGdfxMagic, kMagicLen))
{
out.type = FilesystemType::GDFX;
out.description = "GDFX (Xbox Game Disc)";
return true;
}
}
return false;
}
// ============================================================================
// PS2 Memory Card detection
// Magic: "Sony PS2 Memory Card Format " at offset 0
// ============================================================================
bool FilesystemDetector::detectPs2mc(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 128);
if (data.size() < 40) return false;
// Check the first 28 bytes of the magic string
if (!memEqual(data.data(), "Sony PS2 Memory Card Format ", 28))
return false;
out.type = FilesystemType::PS2MC;
out.description = "PS2 Memory Card";
// Page size at offset 0x28 (uint16)
if (data.size() >= 0x2C)
{
uint16_t pageSize = readLE16(data.data() + 0x28);
uint16_t pagesPerCluster = readLE16(data.data() + 0x2A);
if (pageSize > 0 && pagesPerCluster > 0)
out.blockSize = pageSize * pagesPerCluster;
}
return true;
}
// ============================================================================
// VHD detection (Microsoft Virtual Hard Disk)
// Footer magic: "conectix" at last 512 bytes of file, or at offset 0 for fixed VHD
// ============================================================================
bool FilesystemDetector::detectVhd(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize)
{
// VHD footer can be at offset 0 (dynamic/differencing) as a copy
auto data = safeRead(readFunc, 0, 512);
if (data.size() < 512) return false;
if (memEqual(data.data(), "conectix", 8))
{
out.type = FilesystemType::VHD;
out.description = "VHD (Virtual Hard Disk)";
return true;
}
// For fixed VHD, footer is at the very end — check if volumeSize is known
if (volumeSize >= 512)
{
auto footer = safeRead(readFunc, volumeSize - 512, 512);
if (footer.size() >= 8 && memEqual(footer.data(), "conectix", 8))
{
out.type = FilesystemType::VHD;
out.description = "VHD (Virtual Hard Disk)";
return true;
}
}
return false;
}
// ============================================================================
// VHDX detection
// Magic: "vhdxfile" at offset 0
// ============================================================================
bool FilesystemDetector::detectVhdx(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 16);
if (data.size() < 8) return false;
if (memEqual(data.data(), "vhdxfile", 8))
{
out.type = FilesystemType::VHDX;
out.description = "VHDX (Hyper-V Virtual Hard Disk)";
return true;
}
return false;
}
// ============================================================================
// VMDK detection
// Magic: "KDMV" (0x564D444B LE) for sparse extent, or "# Disk DescriptorFile" text
// ============================================================================
bool FilesystemDetector::detectVmdk(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 64);
if (data.size() < 4) return false;
// Sparse VMDK: magic "KDMV" at offset 0
uint32_t magic = readLE32(data.data());
if (magic == 0x564D444B) // "KDMV" LE
{
out.type = FilesystemType::VMDK;
out.description = "VMDK (VMware Virtual Disk)";
return true;
}
// Text descriptor VMDK
if (data.size() >= 21 && memEqual(data.data(), "# Disk DescriptorFile", 21))
{
out.type = FilesystemType::VMDK;
out.description = "VMDK (VMware Virtual Disk)";
return true;
}
return false;
}
// ============================================================================
// QCOW2 detection
// Magic: "QFI\xFB" (0x514649FB BE) at offset 0
// ============================================================================
bool FilesystemDetector::detectQcow2(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 16);
if (data.size() < 8) return false;
uint32_t magic = readBE32(data.data());
if (magic != 0x514649FB)
return false;
uint32_t version = readBE32(data.data() + 4);
if (version != 2 && version != 3)
return false;
out.type = FilesystemType::QCOW2;
out.description = (version == 3) ? "QCOW2 v3 (QEMU)" : "QCOW2 (QEMU)";
return true;
}
// ============================================================================
// VDI detection (VirtualBox)
// Magic: 0xBEDA107F at offset 0x40
// ============================================================================
bool FilesystemDetector::detectVdi(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 0x50);
if (data.size() < 0x48) return false;
// VDI signature at offset 0x40
uint32_t magic = readLE32(data.data() + 0x40);
if (magic != 0xBEDA107F)
return false;
out.type = FilesystemType::VDI;
out.description = "VDI (VirtualBox Disk Image)";
// Check for "<<< " image creation marker at offset 0
if (memEqual(data.data(), "\x7F\x10\xDA\xBE", 4) ||
memEqual(data.data() + 0x40, "\x7F\x10\xDA\xBE", 4))
{
// Already matched
}
return true;
}
// ============================================================================
// RVZ detection (Dolphin Wii disc image)
// Magic: 0x015A5652 LE ("RVZ\x01") at offset 0
// Also WIA: 0x01414957 LE ("WIA\x01")
// ============================================================================
bool FilesystemDetector::detectRvz(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 8);
if (data.size() < 4) return false;
uint32_t magic = readLE32(data.data());
if (magic == 0x015A5652) // "RVZ\x01"
{
out.type = FilesystemType::RVZ;
out.description = "RVZ (Dolphin Wii Disc Image)";
return true;
}
// WIA format (predecessor to RVZ, same family)
if (magic == 0x01414957) // "WIA\x01"
{
out.type = FilesystemType::RVZ;
out.description = "WIA (Dolphin Wii Disc Image)";
return true;
}
return false;
}
// ============================================================================
// NRG detection (Nero disc image)
// Magic: "NER5" or "NERO" at end of file (footer-based)
// ============================================================================
bool FilesystemDetector::detectNrg(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize)
{
if (volumeSize < 12)
return false;
// NRG v2: "NER5" at (filesize - 12)
auto footer = safeRead(readFunc, volumeSize - 12, 12);
if (footer.size() >= 4)
{
if (memEqual(footer.data(), "NER5", 4))
{
out.type = FilesystemType::NRG;
out.description = "NRG v2 (Nero Disc Image)";
return true;
}
}
// NRG v1: "NERO" at (filesize - 8)
footer = safeRead(readFunc, volumeSize - 8, 8);
if (footer.size() >= 4)
{
if (memEqual(footer.data(), "NERO", 4))
{
out.type = FilesystemType::NRG;
out.description = "NRG v1 (Nero Disc Image)";
return true;
}
}
return false;
}
// ============================================================================
// WBFS detection (Wii Backup File System)
// Magic: "WBFS" at offset 0
// ============================================================================
bool FilesystemDetector::detectWbfs(const DiskReadCallback& readFunc, FilesystemDetection& out)
{
auto data = safeRead(readFunc, 0, 16);
if (data.size() < 4) return false;
if (memEqual(data.data(), "WBFS", 4))
{
out.type = FilesystemType::WBFs;
out.description = "WBFS (Wii Backup File System)";
return true;
}
return false;
}
} // namespace spw } // namespace spw

View File

@@ -85,6 +85,29 @@ private:
static bool detectRomFs(const DiskReadCallback& readFunc, FilesystemDetection& out); static bool detectRomFs(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectLinuxSwap(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize); static bool detectLinuxSwap(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize);
// Flash-optimized
static bool detectF2fs(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectJffs2(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectNilfs2(const DiskReadCallback& readFunc, FilesystemDetection& out);
// Console / gaming
static bool detectFatx(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectStfs(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectGdfx(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectPs2mc(const DiskReadCallback& readFunc, FilesystemDetection& out);
// Virtual disk images
static bool detectVhd(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize);
static bool detectVhdx(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectVmdk(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectQcow2(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectVdi(const DiskReadCallback& readFunc, FilesystemDetection& out);
// Disc images
static bool detectRvz(const DiskReadCallback& readFunc, FilesystemDetection& out);
static bool detectNrg(const DiskReadCallback& readFunc, FilesystemDetection& out, uint64_t volumeSize);
static bool detectWbfs(const DiskReadCallback& readFunc, FilesystemDetection& out);
// Helper: safely read bytes through the callback, returning empty vector on failure // Helper: safely read bytes through the callback, returning empty vector on failure
static std::vector<uint8_t> safeRead(const DiskReadCallback& readFunc, uint64_t offset, uint32_t size); static std::vector<uint8_t> safeRead(const DiskReadCallback& readFunc, uint64_t offset, uint32_t size);
}; };

View File

@@ -0,0 +1,500 @@
#include "SdCardRecovery.h"
#include "../disk/RawDiskHandle.h"
#include "../disk/DiskEnumerator.h"
#include "../common/Logging.h"
#include <windows.h>
#include <winioctl.h>
#include <cstring>
#include <cwctype>
#include <algorithm>
namespace spw
{
bool SdCardRecovery::looksLikeSdCard(const DiskInfo& disk)
{
// SD/MMC bus type
if (disk.interfaceType == DiskInterfaceType::MMC)
return true;
// Removable + small size (up to 2TB covers SDXC)
if (disk.isRemovable && disk.sizeBytes > 0 && disk.sizeBytes <= 2199023255552ULL)
{
// Check model string for SD/MMC keywords
std::wstring modelLower = disk.model;
std::transform(modelLower.begin(), modelLower.end(), modelLower.begin(),
[](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
if (modelLower.find(L"sd") != std::wstring::npos ||
modelLower.find(L"mmc") != std::wstring::npos ||
modelLower.find(L"sdhc") != std::wstring::npos ||
modelLower.find(L"sdxc") != std::wstring::npos ||
modelLower.find(L"micro") != std::wstring::npos ||
modelLower.find(L"card") != std::wstring::npos ||
modelLower.find(L"reader") != std::wstring::npos)
{
return true;
}
// Also match USB removable devices under ~256GB (likely USB card readers)
if (disk.interfaceType == DiskInterfaceType::USB)
return true;
}
return false;
}
Result<SdCardInfo> SdCardRecovery::analyzeDisk(DiskId diskId)
{
SdCardInfo info;
info.diskId = diskId;
// Get disk info from enumerator
auto diskInfoResult = DiskEnumerator::getDiskInfo(diskId);
if (diskInfoResult.isError())
return diskInfoResult.error();
const auto& di = diskInfoResult.value();
info.model = di.model;
info.serialNumber = di.serialNumber;
info.sizeBytes = di.sizeBytes;
info.sectorSize = di.sectorSize;
info.interfaceType = di.interfaceType;
if (info.sizeBytes == 0)
{
info.status = SdCardStatus::NoMedia;
info.statusDescription = L"Card reader detected but no media inserted";
return info;
}
// Try to open the disk and read partition table
auto diskResult = RawDiskHandle::open(diskId, DiskAccessMode::ReadOnly);
if (diskResult.isError())
{
info.status = SdCardStatus::Unknown;
info.statusDescription = L"Cannot open disk for analysis";
return info;
}
auto& disk = diskResult.value();
// Try to get drive layout
auto layoutResult = disk.getDriveLayout();
if (layoutResult.isError())
{
// No valid partition table at all
info.status = SdCardStatus::NoPartitionTable;
info.statusDescription = L"No valid partition table found (MBR or GPT)";
info.hasPartitions = false;
return info;
}
const auto& layout = layoutResult.value();
// Check for valid partitions
int validPartitions = 0;
for (const auto& part : layout.partitions)
{
if (part.partitionLength > 0 && part.partitionNumber > 0)
++validPartitions;
}
if (validPartitions == 0)
{
info.status = SdCardStatus::CorruptPartition;
info.statusDescription = L"Partition table exists but contains no valid entries";
info.hasPartitions = false;
return info;
}
info.hasPartitions = true;
// Check if any partition has a drive letter (visible to Windows)
auto snapshotResult = DiskEnumerator::getSystemSnapshot();
if (snapshotResult.isOk())
{
for (const auto& part : snapshotResult.value().partitions)
{
if (part.diskId == diskId && part.driveLetter != L'\0')
{
info.hasDriveLetter = true;
info.driveLetter = part.driveLetter;
break;
}
}
// Check if filesystem is recognized
bool hasRecognizedFs = false;
for (const auto& part : snapshotResult.value().partitions)
{
if (part.diskId == diskId &&
part.filesystemType != FilesystemType::Unknown &&
part.filesystemType != FilesystemType::Unallocated)
{
hasRecognizedFs = true;
break;
}
}
if (!hasRecognizedFs)
{
info.status = SdCardStatus::RawFilesystem;
info.statusDescription = L"Partition exists but filesystem is RAW or unrecognized";
return info;
}
}
info.status = SdCardStatus::Healthy;
info.statusDescription = L"Card is healthy";
return info;
}
Result<std::vector<SdCardInfo>> SdCardRecovery::detectSdCards()
{
std::vector<SdCardInfo> cards;
auto disksResult = DiskEnumerator::enumerateDisks();
if (disksResult.isError())
return disksResult.error();
for (const auto& disk : disksResult.value())
{
if (!looksLikeSdCard(disk))
continue;
auto analysisResult = analyzeDisk(disk.id);
if (analysisResult.isOk())
cards.push_back(std::move(analysisResult.value()));
}
// Also try to find disks that SetupAPI detects but enumerateDisks might
// miss due to having zero partitions — scan PhysicalDrive0..31 directly
for (int i = 0; i < 32; ++i)
{
// Skip if we already found this disk
bool found = false;
for (const auto& card : cards)
{
if (card.diskId == i)
{
found = true;
break;
}
}
if (found)
continue;
// Try to open the disk directly
auto diskResult = RawDiskHandle::open(i, DiskAccessMode::ReadOnly);
if (diskResult.isError())
continue;
auto& disk = diskResult.value();
auto geomResult = disk.getGeometry();
if (geomResult.isError())
continue;
// Check if it's removable media
if (geomResult.value().mediaType == RemovableMedia ||
geomResult.value().mediaType == FixedMedia)
{
// Only include small removable disks (likely SD cards)
auto totalBytes = geomResult.value().totalBytes;
if (totalBytes > 0 && totalBytes <= 2199023255552ULL)
{
auto analysisResult = analyzeDisk(i);
if (analysisResult.isOk())
{
auto& info = analysisResult.value();
if (info.model.empty())
info.model = L"Unknown Removable Disk";
cards.push_back(std::move(info));
}
}
}
}
return cards;
}
Result<void> SdCardRecovery::cleanDisk(RawDiskHandle& disk, uint64_t diskSize)
{
HANDLE hDisk = disk.nativeHandle();
// First zero out the first and last 1MB to destroy any existing
// partition tables (MBR at sector 0, GPT at sector 1 + backup at end)
constexpr uint64_t kCleanSize = 1048576; // 1 MB
std::vector<uint8_t> zeros(static_cast<size_t>(kCleanSize), 0);
// Zero the beginning
LARGE_INTEGER offset;
offset.QuadPart = 0;
if (!SetFilePointerEx(hDisk, offset, nullptr, FILE_BEGIN))
return ErrorInfo::fromWin32(ErrorCode::DiskWriteError, GetLastError(),
"Failed to seek to disk start");
DWORD written = 0;
if (!WriteFile(hDisk, zeros.data(), static_cast<DWORD>(kCleanSize), &written, nullptr))
return ErrorInfo::fromWin32(ErrorCode::DiskWriteError, GetLastError(),
"Failed to zero disk start");
// Zero the end (backup GPT)
if (diskSize > kCleanSize)
{
offset.QuadPart = static_cast<LONGLONG>(diskSize - kCleanSize);
if (SetFilePointerEx(hDisk, offset, nullptr, FILE_BEGIN))
{
WriteFile(hDisk, zeros.data(), static_cast<DWORD>(kCleanSize), &written, nullptr);
// Failure to zero end is non-fatal
}
}
// Now use IOCTL_DISK_CREATE_DISK to create a fresh MBR
CREATE_DISK createDisk;
std::memset(&createDisk, 0, sizeof(createDisk));
createDisk.PartitionStyle = PARTITION_STYLE_MBR;
createDisk.Mbr.Signature = GetTickCount(); // Random-ish signature
DWORD bytesReturned = 0;
if (!DeviceIoControl(hDisk, IOCTL_DISK_CREATE_DISK,
&createDisk, sizeof(createDisk),
nullptr, 0, &bytesReturned, nullptr))
{
return ErrorInfo::fromWin32(ErrorCode::DiskWriteError, GetLastError(),
"IOCTL_DISK_CREATE_DISK failed");
}
return Result<void>::ok();
}
Result<void> SdCardRecovery::createPartition(RawDiskHandle& disk, uint64_t diskSize,
uint32_t sectorSize, FilesystemType fs)
{
HANDLE hDisk = disk.nativeHandle();
// Allocate buffer for DRIVE_LAYOUT_INFORMATION_EX with 4 MBR entries
size_t bufSize = sizeof(DRIVE_LAYOUT_INFORMATION_EX)
+ 3 * sizeof(PARTITION_INFORMATION_EX);
std::vector<uint8_t> buf(bufSize, 0);
auto* layout = reinterpret_cast<DRIVE_LAYOUT_INFORMATION_EX*>(buf.data());
layout->PartitionStyle = PARTITION_STYLE_MBR;
layout->PartitionCount = 4; // MBR always has 4 entries
layout->Mbr.Signature = GetTickCount();
// Single partition starting at 1MB offset (aligned)
uint64_t partOffset = 1048576; // 1 MB alignment
if (partOffset < static_cast<uint64_t>(sectorSize))
partOffset = sectorSize;
uint64_t partLength = diskSize - partOffset;
// Align partition length down to sector boundary
partLength = (partLength / sectorSize) * sectorSize;
auto& part = layout->PartitionEntry[0];
part.PartitionStyle = PARTITION_STYLE_MBR;
part.StartingOffset.QuadPart = static_cast<LONGLONG>(partOffset);
part.PartitionLength.QuadPart = static_cast<LONGLONG>(partLength);
part.PartitionNumber = 1;
part.RewritePartition = TRUE;
// Set MBR partition type
switch (fs)
{
case FilesystemType::FAT32:
// FAT32 LBA
part.Mbr.PartitionType = partLength > 4294967296ULL ? 0x0C : 0x0B;
break;
case FilesystemType::ExFAT:
part.Mbr.PartitionType = 0x07; // exFAT uses type 0x07 same as NTFS
break;
case FilesystemType::NTFS:
part.Mbr.PartitionType = 0x07;
break;
default:
part.Mbr.PartitionType = 0x0B; // Default to FAT32
break;
}
part.Mbr.BootIndicator = FALSE;
part.Mbr.RecognizedPartition = TRUE;
// Zero out the remaining 3 MBR entries
for (int i = 1; i < 4; ++i)
{
layout->PartitionEntry[i].RewritePartition = TRUE;
}
DWORD bytesReturned = 0;
if (!DeviceIoControl(hDisk, IOCTL_DISK_SET_DRIVE_LAYOUT_EX,
buf.data(), static_cast<DWORD>(bufSize),
nullptr, 0, &bytesReturned, nullptr))
{
return ErrorInfo::fromWin32(ErrorCode::DiskWriteError, GetLastError(),
"IOCTL_DISK_SET_DRIVE_LAYOUT_EX failed");
}
return Result<void>::ok();
}
Result<void> SdCardRecovery::rescanDisk(RawDiskHandle& disk)
{
HANDLE hDisk = disk.nativeHandle();
DWORD bytesReturned = 0;
// Tell Windows to re-read the partition table
if (!DeviceIoControl(hDisk, IOCTL_DISK_UPDATE_PROPERTIES,
nullptr, 0, nullptr, 0, &bytesReturned, nullptr))
{
return ErrorInfo::fromWin32(ErrorCode::DiskWriteError, GetLastError(),
"IOCTL_DISK_UPDATE_PROPERTIES failed");
}
return Result<void>::ok();
}
Result<void> SdCardRecovery::formatPartition(DiskId diskId, FilesystemType fs,
const std::wstring& label)
{
// Wait for Windows to assign a drive letter after partition creation
Sleep(2000);
// Find the drive letter for the new partition
auto snapshotResult = DiskEnumerator::getSystemSnapshot();
if (snapshotResult.isError())
return snapshotResult.error();
wchar_t driveLetter = L'\0';
for (const auto& part : snapshotResult.value().partitions)
{
if (part.diskId == diskId && part.driveLetter != L'\0')
{
driveLetter = part.driveLetter;
break;
}
}
if (driveLetter == L'\0')
{
return ErrorInfo::fromCode(ErrorCode::DiskNotFound,
"Windows did not assign a drive letter. "
"Open Disk Management and assign a letter, then format manually.");
}
// Build format command
std::wstring fsName;
switch (fs)
{
case FilesystemType::FAT32: fsName = L"FAT32"; break;
case FilesystemType::ExFAT: fsName = L"exFAT"; break;
case FilesystemType::NTFS: fsName = L"NTFS"; break;
default: fsName = L"FAT32"; break;
}
// format X: /FS:FAT32 /Q /V:label /Y
std::wstring cmd = L"format " + std::wstring(1, driveLetter) + L": /FS:" + fsName
+ L" /Q /V:" + label + L" /Y";
STARTUPINFOW si = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = {};
if (!CreateProcessW(nullptr, cmd.data(), nullptr, nullptr, FALSE,
CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
{
return ErrorInfo::fromWin32(ErrorCode::FormatFailed, GetLastError(),
"Failed to launch format command");
}
// Wait up to 60 seconds for format to complete
DWORD waitResult = WaitForSingleObject(pi.hProcess, 60000);
DWORD exitCode = 1;
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (waitResult == WAIT_TIMEOUT)
return ErrorInfo::fromCode(ErrorCode::FormatFailed, "Format command timed out");
if (exitCode != 0)
return ErrorInfo::fromCode(ErrorCode::FormatFailed,
"Format command failed with exit code " + std::to_string(exitCode));
return Result<void>::ok();
}
Result<void> SdCardRecovery::fixCard(DiskId diskId, const SdFixConfig& config,
SdProgressCallback progress)
{
auto report = [&progress](const std::string& stage, int pct) {
if (progress)
progress(stage, pct);
};
report("Opening disk...", 0);
auto diskResult = RawDiskHandle::open(diskId, DiskAccessMode::ReadWrite);
if (diskResult.isError())
return diskResult.error();
auto& disk = diskResult.value();
// Get geometry for disk size
auto geomResult = disk.getGeometry();
if (geomResult.isError())
return geomResult.error();
uint64_t diskSize = geomResult.value().totalBytes;
uint32_t sectorSize = geomResult.value().bytesPerSector;
if (diskSize == 0)
return ErrorInfo::fromCode(ErrorCode::InvalidArgument, "Disk reports zero size - no media?");
// Auto-select filesystem based on size if FAT32 requested on >32GB card
FilesystemType targetFs = config.targetFs;
if (targetFs == FilesystemType::FAT32 && diskSize > 34359738368ULL) // >32GB
targetFs = FilesystemType::ExFAT;
if (config.action == SdFixAction::CleanAndFormat ||
config.action == SdFixAction::ReinitPartitionOnly)
{
report("Cleaning disk (zeroing partition tables)...", 10);
auto cleanResult = cleanDisk(disk, diskSize);
if (cleanResult.isError())
return cleanResult;
report("Creating partition table...", 30);
auto partResult = createPartition(disk, diskSize, sectorSize, targetFs);
if (partResult.isError())
return partResult;
report("Updating disk properties...", 50);
auto rescanResult = rescanDisk(disk);
if (rescanResult.isError())
{
log::warn(("Rescan failed (non-fatal): " + rescanResult.error().message).c_str());
}
// Close the disk handle before formatting so Windows can access it
disk.close();
}
if (config.action == SdFixAction::CleanAndFormat ||
config.action == SdFixAction::FormatOnly)
{
report("Formatting partition...", 60);
auto fmtResult = formatPartition(diskId, targetFs, config.volumeLabel);
if (fmtResult.isError())
return fmtResult;
}
report("Done!", 100);
return Result<void>::ok();
}
} // namespace spw

View File

@@ -0,0 +1,106 @@
#pragma once
// SdCardRecovery — Detects and repairs SD cards that Windows cannot see.
// Handles cards with corrupted partition tables (e.g., from interrupted format),
// RAW/uninitialized cards, and cards with no valid filesystem.
// Uses raw disk I/O via IOCTL_DISK_CREATE_DISK and IOCTL_DISK_SET_DRIVE_LAYOUT_EX
// to reinitialize partition tables without relying on Windows volume management.
#include <windows.h>
#include <winioctl.h>
#include "../common/Error.h"
#include "../common/Result.h"
#include "../common/Types.h"
#include "../disk/DiskEnumerator.h"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
namespace spw
{
class RawDiskHandle;
// Status of an SD card as detected by the recovery tool
enum class SdCardStatus
{
Healthy, // Card is fine, has valid partition table and filesystem
NoPartitionTable, // No valid MBR or GPT found
CorruptPartition, // Partition table exists but is damaged
RawFilesystem, // Partition exists but filesystem is RAW/unrecognized
NoMedia, // Card reader detected but no card inserted
Unknown
};
// Information about a detected SD card (may or may not be visible to Windows)
struct SdCardInfo
{
DiskId diskId = -1;
std::wstring model;
std::wstring serialNumber;
uint64_t sizeBytes = 0;
uint32_t sectorSize = 512;
SdCardStatus status = SdCardStatus::Unknown;
std::wstring statusDescription;
bool hasPartitions = false;
bool hasDriveLetter = false;
wchar_t driveLetter = L'\0';
DiskInterfaceType interfaceType = DiskInterfaceType::Unknown;
};
// What to do when fixing the card
enum class SdFixAction
{
CleanAndFormat, // Wipe partition table, create new MBR + single FAT32/exFAT partition
ReinitPartitionOnly, // Just rewrite the partition table, don't format
FormatOnly // Keep partition table, just format the existing partition
};
struct SdFixConfig
{
SdFixAction action = SdFixAction::CleanAndFormat;
FilesystemType targetFs = FilesystemType::FAT32; // FAT32 for <= 32GB, exFAT for > 32GB
std::wstring volumeLabel = L"SD_CARD";
bool quickFormat = true;
};
using SdProgressCallback = std::function<void(const std::string& stage, int percentComplete)>;
class SdCardRecovery
{
public:
// Scan the system for SD/MMC card readers and cards, including those
// that Windows doesn't assign drive letters to.
// This uses SetupAPI directly, not just volume enumeration.
static Result<std::vector<SdCardInfo>> detectSdCards();
// Analyze a specific disk to determine its SD card status
static Result<SdCardInfo> analyzeDisk(DiskId diskId);
// Fix an SD card: clean partition table, create new partition, and format
static Result<void> fixCard(DiskId diskId, const SdFixConfig& config,
SdProgressCallback progress = nullptr);
private:
// Clean the disk by creating a fresh MBR or GPT
static Result<void> cleanDisk(RawDiskHandle& disk, uint64_t diskSize);
// Create a single partition spanning the entire disk
static Result<void> createPartition(RawDiskHandle& disk, uint64_t diskSize,
uint32_t sectorSize, FilesystemType fs);
// Quick format the new partition
static Result<void> formatPartition(DiskId diskId, FilesystemType fs,
const std::wstring& label);
// Force Windows to rescan the disk for new partitions
static Result<void> rescanDisk(RawDiskHandle& disk);
// Check if a disk looks like an SD card based on bus type, removability, size
static bool looksLikeSdCard(const DiskInfo& disk);
};
} // namespace spw

View File

@@ -233,8 +233,8 @@ void DiagnosticsTab::displaySmartData(const SmartData& data)
{ {
case SmartStatus::OK: case SmartStatus::OK:
healthText = tr("PASSED - Healthy"); healthText = tr("PASSED - Healthy");
healthColor = QColor(0, 180, 0); healthColor = QColor(212, 165, 116);
m_healthIcon->setStyleSheet("background-color: #00b400; border-radius: 24px;"); m_healthIcon->setStyleSheet("background-color: #d4a574; border-radius: 24px;");
break; break;
case SmartStatus::Warning: case SmartStatus::Warning:
healthText = tr("WARNING - Issues Detected"); healthText = tr("WARNING - Issues Detected");
@@ -523,7 +523,7 @@ QColor DiagnosticsTab::smartStatusColor(SmartStatus status)
{ {
switch (status) switch (status)
{ {
case SmartStatus::OK: return QColor(0, 180, 0); case SmartStatus::OK: return QColor(212, 165, 116);
case SmartStatus::Warning: return QColor(255, 180, 0); case SmartStatus::Warning: return QColor(255, 180, 0);
case SmartStatus::Critical: return QColor(255, 0, 0); case SmartStatus::Critical: return QColor(255, 0, 0);
default: return QColor(128, 128, 128); default: return QColor(128, 128, 128);

View File

@@ -3,6 +3,7 @@
#include "core/disk/DiskEnumerator.h" #include "core/disk/DiskEnumerator.h"
#include "core/disk/RawDiskHandle.h" #include "core/disk/RawDiskHandle.h"
#include "core/maintenance/SecureErase.h" #include "core/maintenance/SecureErase.h"
#include "core/maintenance/SdCardRecovery.h"
#include "core/recovery/BootRepair.h" #include "core/recovery/BootRepair.h"
#include <QCheckBox> #include <QCheckBox>
@@ -137,6 +138,60 @@ void MaintenanceTab::setupUi()
bootLayout->addWidget(m_bootStatusLabel); bootLayout->addWidget(m_bootStatusLabel);
layout->addWidget(bootGroup); layout->addWidget(bootGroup);
// ===== SD Card Recovery Section =====
auto* sdGroup = new QGroupBox(tr("SD Card Recovery"));
auto* sdLayout = new QGridLayout(sdGroup);
auto* sdInfo = new QLabel(
tr("Detect and fix SD/microSD cards that Windows cannot see.\n"
"Repairs corrupted partition tables from interrupted formats, "
"RAW cards, and uninitialized media."));
sdInfo->setWordWrap(true);
sdLayout->addWidget(sdInfo, 0, 0, 1, 3);
m_sdScanBtn = new QPushButton(tr("Scan for SD Cards"));
m_sdScanBtn->setToolTip(tr("Scan all disk interfaces for SD/MMC cards, including invisible ones"));
connect(m_sdScanBtn, &QPushButton::clicked, this, &MaintenanceTab::onSdScan);
sdLayout->addWidget(m_sdScanBtn, 1, 0);
m_sdCardCombo = new QComboBox();
sdLayout->addWidget(m_sdCardCombo, 1, 1, 1, 2);
sdLayout->addWidget(new QLabel(tr("Format As:")), 2, 0);
m_sdFsCombo = new QComboBox();
m_sdFsCombo->addItems({
tr("FAT32 (recommended for <= 32 GB)"),
tr("exFAT (recommended for > 32 GB)"),
tr("NTFS")
});
sdLayout->addWidget(m_sdFsCombo, 2, 1, 1, 2);
sdLayout->addWidget(new QLabel(tr("Volume Label:")), 3, 0);
m_sdLabelEdit = new QLineEdit(QStringLiteral("SD_CARD"));
m_sdLabelEdit->setMaxLength(11);
sdLayout->addWidget(m_sdLabelEdit, 3, 1, 1, 2);
m_sdFixBtn = new QPushButton(tr("Fix SD Card"));
m_sdFixBtn->setMinimumHeight(40);
m_sdFixBtn->setStyleSheet(
"QPushButton { background-color: #d4a574; color: #1e1e2e; font-size: 14px; "
"font-weight: bold; border: 2px solid #b08050; border-radius: 6px; }"
"QPushButton:hover { background-color: #e0b584; }"
"QPushButton:pressed { background-color: #c49060; }");
m_sdFixBtn->setEnabled(false);
connect(m_sdFixBtn, &QPushButton::clicked, this, &MaintenanceTab::onSdFix);
sdLayout->addWidget(m_sdFixBtn, 4, 0, 1, 3);
m_sdProgress = new QProgressBar();
m_sdProgress->setVisible(false);
sdLayout->addWidget(m_sdProgress, 5, 0, 1, 3);
m_sdStatusLabel = new QLabel();
m_sdStatusLabel->setWordWrap(true);
sdLayout->addWidget(m_sdStatusLabel, 6, 0, 1, 3);
layout->addWidget(sdGroup);
layout->addStretch(); layout->addStretch();
} }
@@ -465,6 +520,149 @@ void MaintenanceTab::onReinstallBootloader()
thread->start(); thread->start();
} }
void MaintenanceTab::onSdScan()
{
m_sdScanBtn->setEnabled(false);
m_sdStatusLabel->setText(tr("Scanning for SD cards..."));
m_sdCardCombo->clear();
m_detectedCards.clear();
m_sdFixBtn->setEnabled(false);
auto* thread = QThread::create([this]() {
auto result = SdCardRecovery::detectSdCards();
if (result.isError())
{
QMetaObject::invokeMethod(m_sdStatusLabel, "setText",
Qt::QueuedConnection,
Q_ARG(QString, tr("Scan failed: %1")
.arg(QString::fromStdString(result.error().message))));
return;
}
m_detectedCards = std::move(result.value());
});
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &QThread::finished, this, [this]() {
m_sdScanBtn->setEnabled(true);
if (m_detectedCards.empty())
{
m_sdStatusLabel->setText(
tr("No SD/MMC cards detected.\n"
"Make sure the card is inserted in a reader and the reader is connected."));
return;
}
for (const auto& card : m_detectedCards)
{
QString statusStr;
switch (card.status)
{
case SdCardStatus::Healthy: statusStr = tr("Healthy"); break;
case SdCardStatus::NoPartitionTable: statusStr = tr("NO PARTITION TABLE"); break;
case SdCardStatus::CorruptPartition: statusStr = tr("CORRUPT"); break;
case SdCardStatus::RawFilesystem: statusStr = tr("RAW"); break;
case SdCardStatus::NoMedia: statusStr = tr("No Media"); break;
default: statusStr = tr("Unknown"); break;
}
QString label = QString("Disk %1: %2 (%3) [%4]")
.arg(card.diskId)
.arg(QString::fromStdWString(card.model))
.arg(formatSize(card.sizeBytes))
.arg(statusStr);
m_sdCardCombo->addItem(label, card.diskId);
}
m_sdFixBtn->setEnabled(true);
m_sdStatusLabel->setText(
tr("Found %1 SD card(s). Select one and click Fix to repair.")
.arg(m_detectedCards.size()));
emit statusMessage(tr("SD card scan complete — %1 card(s) found")
.arg(m_detectedCards.size()));
});
thread->start();
}
void MaintenanceTab::onSdFix()
{
int diskId = m_sdCardCombo->currentData().toInt();
// Find the card info
const SdCardInfo* cardInfo = nullptr;
for (const auto& card : m_detectedCards)
{
if (card.diskId == diskId)
{
cardInfo = &card;
break;
}
}
if (!cardInfo)
return;
// Confirmation
auto reply = QMessageBox::warning(this, tr("Fix SD Card"),
tr("This will ERASE ALL DATA on:\n\n"
"Disk %1: %2 (%3)\n\n"
"The card will be cleaned, repartitioned, and formatted.\n\n"
"Continue?")
.arg(diskId)
.arg(QString::fromStdWString(cardInfo->model))
.arg(formatSize(cardInfo->sizeBytes)),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes)
return;
// Build config
SdFixConfig config;
config.action = SdFixAction::CleanAndFormat;
switch (m_sdFsCombo->currentIndex())
{
case 0: config.targetFs = FilesystemType::FAT32; break;
case 1: config.targetFs = FilesystemType::ExFAT; break;
case 2: config.targetFs = FilesystemType::NTFS; break;
}
config.volumeLabel = m_sdLabelEdit->text().toStdWString();
m_sdFixBtn->setEnabled(false);
m_sdScanBtn->setEnabled(false);
m_sdProgress->setVisible(true);
m_sdProgress->setValue(0);
m_sdStatusLabel->setText(tr("Fixing SD card..."));
auto* thread = QThread::create([this, diskId, config]() {
auto result = SdCardRecovery::fixCard(diskId, config,
[this](const std::string& stage, int pct) {
QMetaObject::invokeMethod(m_sdProgress, "setValue",
Qt::QueuedConnection, Q_ARG(int, pct));
QMetaObject::invokeMethod(m_sdStatusLabel, "setText",
Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(stage)));
});
if (result.isError())
{
QMetaObject::invokeMethod(m_sdStatusLabel, "setText",
Qt::QueuedConnection,
Q_ARG(QString, tr("Fix failed: %1")
.arg(QString::fromStdString(result.error().message))));
}
});
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &QThread::finished, this, [this]() {
m_sdProgress->setVisible(false);
m_sdFixBtn->setEnabled(true);
m_sdScanBtn->setEnabled(true);
emit statusMessage(tr("SD card fix completed"));
});
thread->start();
}
QString MaintenanceTab::formatSize(uint64_t bytes) QString MaintenanceTab::formatSize(uint64_t bytes)
{ {
if (bytes >= 1099511627776ULL) if (bytes >= 1099511627776ULL)

View File

@@ -3,6 +3,7 @@
#include "core/common/Types.h" #include "core/common/Types.h"
#include "core/disk/DiskEnumerator.h" #include "core/disk/DiskEnumerator.h"
#include "core/maintenance/SecureErase.h" #include "core/maintenance/SecureErase.h"
#include "core/maintenance/SdCardRecovery.h"
#include <QWidget> #include <QWidget>
#include <atomic> #include <atomic>
@@ -40,6 +41,8 @@ private slots:
void onRepairGpt(); void onRepairGpt();
void onRepairBcd(); void onRepairBcd();
void onReinstallBootloader(); void onReinstallBootloader();
void onSdScan();
void onSdFix();
private: private:
void setupUi(); void setupUi();
@@ -65,6 +68,16 @@ private:
QProgressBar* m_bootProgress = nullptr; QProgressBar* m_bootProgress = nullptr;
QLabel* m_bootStatusLabel = nullptr; QLabel* m_bootStatusLabel = nullptr;
// SD Card Recovery
QComboBox* m_sdCardCombo = nullptr;
QPushButton* m_sdScanBtn = nullptr;
QPushButton* m_sdFixBtn = nullptr;
QComboBox* m_sdFsCombo = nullptr;
QLineEdit* m_sdLabelEdit = nullptr;
QLabel* m_sdStatusLabel = nullptr;
QProgressBar* m_sdProgress = nullptr;
std::vector<SdCardInfo> m_detectedCards;
// Data // Data
SystemDiskSnapshot m_snapshot; SystemDiskSnapshot m_snapshot;
std::atomic<bool> m_cancelFlag{false}; std::atomic<bool> m_cancelFlag{false};

View File

@@ -291,10 +291,10 @@ QColor DiskMapWidget::colorForFilesystem(FilesystemType fs)
switch (fs) switch (fs)
{ {
case FilesystemType::NTFS: return QColor(52, 101, 164); case FilesystemType::NTFS: return QColor(52, 101, 164);
case FilesystemType::FAT32: return QColor(78, 154, 6); case FilesystemType::FAT32: return QColor(180, 145, 120);
case FilesystemType::FAT16: return QColor(78, 154, 6); case FilesystemType::FAT16: return QColor(180, 145, 120);
case FilesystemType::FAT12: return QColor(78, 154, 6); case FilesystemType::FAT12: return QColor(180, 145, 120);
case FilesystemType::ExFAT: return QColor(115, 210, 22); case FilesystemType::ExFAT: return QColor(212, 165, 116);
case FilesystemType::ReFS: return QColor(32, 74, 135); case FilesystemType::ReFS: return QColor(32, 74, 135);
case FilesystemType::Ext2: return QColor(204, 0, 0); case FilesystemType::Ext2: return QColor(204, 0, 0);
case FilesystemType::Ext3: return QColor(204, 0, 0); case FilesystemType::Ext3: return QColor(204, 0, 0);
@@ -307,6 +307,29 @@ QColor DiskMapWidget::colorForFilesystem(FilesystemType fs)
case FilesystemType::ISO9660: return QColor(85, 87, 83); case FilesystemType::ISO9660: return QColor(85, 87, 83);
case FilesystemType::UDF: return QColor(85, 87, 83); case FilesystemType::UDF: return QColor(85, 87, 83);
case FilesystemType::Unallocated: return QColor(80, 80, 80); case FilesystemType::Unallocated: return QColor(80, 80, 80);
// Flash-optimized
case FilesystemType::F2FS: return QColor(0, 150, 136); // Teal
case FilesystemType::JFFS2: return QColor(121, 85, 72); // Brown
case FilesystemType::NILFS2: return QColor(158, 157, 36); // Lime
// Console / gaming
case FilesystemType::FATX: return QColor(76, 175, 80); // Xbox green
case FilesystemType::STFS: return QColor(56, 142, 60); // Xbox dark green
case FilesystemType::GDFX: return QColor(46, 125, 50); // Xbox disc green
case FilesystemType::PS2MC: return QColor(33, 150, 243); // PS blue
// Virtual disk images
case FilesystemType::VHD: return QColor(0, 120, 215); // Windows blue
case FilesystemType::VHDX: return QColor(0, 99, 177);
case FilesystemType::VMDK: return QColor(120, 144, 156); // Slate
case FilesystemType::QCOW2: return QColor(255, 87, 34); // Deep orange
case FilesystemType::VDI: return QColor(63, 81, 181); // Indigo
// Disc images
case FilesystemType::RVZ: return QColor(156, 39, 176); // Purple
case FilesystemType::WUA: return QColor(103, 58, 183); // Deep purple
case FilesystemType::WBFs: return QColor(0, 188, 212); // Cyan
case FilesystemType::NRG: return QColor(96, 125, 139); // Blue grey
case FilesystemType::MDF: return QColor(96, 125, 139);
case FilesystemType::CDI: return QColor(96, 125, 139);
case FilesystemType::CDFS: return QColor(85, 87, 83);
default: return QColor(136, 138, 133); default: return QColor(136, 138, 133);
} }
} }
@@ -332,6 +355,25 @@ QString DiskMapWidget::filesystemShortName(FilesystemType fs)
case FilesystemType::SWAP_LINUX: return QStringLiteral("Swap"); case FilesystemType::SWAP_LINUX: return QStringLiteral("Swap");
case FilesystemType::ISO9660: return QStringLiteral("ISO9660"); case FilesystemType::ISO9660: return QStringLiteral("ISO9660");
case FilesystemType::UDF: return QStringLiteral("UDF"); case FilesystemType::UDF: return QStringLiteral("UDF");
case FilesystemType::F2FS: return QStringLiteral("F2FS");
case FilesystemType::JFFS2: return QStringLiteral("JFFS2");
case FilesystemType::NILFS2: return QStringLiteral("NILFS2");
case FilesystemType::FATX: return QStringLiteral("FATX");
case FilesystemType::STFS: return QStringLiteral("STFS");
case FilesystemType::GDFX: return QStringLiteral("GDFX");
case FilesystemType::PS2MC: return QStringLiteral("PS2MC");
case FilesystemType::VHD: return QStringLiteral("VHD");
case FilesystemType::VHDX: return QStringLiteral("VHDX");
case FilesystemType::VMDK: return QStringLiteral("VMDK");
case FilesystemType::QCOW2: return QStringLiteral("QCOW2");
case FilesystemType::VDI: return QStringLiteral("VDI");
case FilesystemType::RVZ: return QStringLiteral("RVZ");
case FilesystemType::WUA: return QStringLiteral("WUA");
case FilesystemType::WBFs: return QStringLiteral("WBFS");
case FilesystemType::NRG: return QStringLiteral("NRG");
case FilesystemType::MDF: return QStringLiteral("MDF");
case FilesystemType::CDI: return QStringLiteral("CDI");
case FilesystemType::CDFS: return QStringLiteral("CDFS");
case FilesystemType::Unallocated: return QStringLiteral("Free"); case FilesystemType::Unallocated: return QStringLiteral("Free");
case FilesystemType::Unknown: return QStringLiteral("Unknown"); case FilesystemType::Unknown: return QStringLiteral("Unknown");
case FilesystemType::Raw: return QStringLiteral("RAW"); case FilesystemType::Raw: return QStringLiteral("RAW");

Binary file not shown.

BIN
virustotal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB