DBXExtractor — Ekstraktor archiwów Outlook Express



DBX Extractor

**Version 1.0.0.1** — Desktopowy ekstraktor poczty Outlook Express w WinAPI + C/C++23 *Odczytuje pliki DBX z archiwów Outlook Express i zapisuje każdą wiadomość jako plik EML* *otwierany bezpośrednio przez Microsoft Outlook.* *Zero zależności zewnętrznych, czysty WinAPI + MSVC, statyczne `/MT`, brak VC++ Redistributable.*

Spis treści


Co robi program

DBXExtractor otwiera pliki .dbx — wewnętrzny format archiwów skrzynki pocztowej Outlook Express (OE 4–6) — i zapisuje każdą przechowaną wiadomość jako osobny plik .eml zgodny z RFC 2822. Pliki EML otwiera bezpośrednio Microsoft Outlook, Thunderbird i każdy inny klient obsługujący standard MIME.

Program działa w dwóch trybach uruchomienia:

Tryb Uruchomienie Zastosowanie
GUI podwójne kliknięcie, bez argumentów interaktywna praca z paskiem postępu i logiem
CLI z argumentami wiersza poleceń skrypty, automatyzacja, integracja z PowerShell

Oba tryby korzystają z tego samego silnika ekstrakcji (Extraction.cpp + dbxread.c); GUI to warstwa prezentacji na wierzchu wspólnego rdzenia.


Tryb GUI

Główne okno (620 × 580 px, WS_OVERLAPPEDWINDOW) zawiera:

Kontrolka Opis
DBX Source ścieżka do folderu z plikami .dbx lub do pojedynczego pliku .dbx
Output Folder folder docelowy; każdy plik DBX otrzymuje własny podfolder
Browse... otwiera SHBrowseForFolderW (folder) lub GetOpenFileNameW (plik)
Extraction Options cztery checkboxy omówione niżej
Verbosity poziom szczegółowości logu (combobox, 6 poziomów)
Extract przycisk owner-draw uruchamiający wątek ekstrakcji
Progress bar PBS_SMOOTH, niewidoczny do czasu startu ekstrakcji
Output Log wieloliniowy ES_READONLY, czcionka Consolas
Status bar SBARS_SIZEGRIP, tekst „Ready" / „Extracting… N%" / „Done."

Opcje ekstrakcji (checkboxy + menu Options)

Każdy checkbox jest zsynchronizowany z odpowiadającą mu pozycją menu Options — zmiana jednego automatycznie aktualizuje drugi.

Opcja Flaga Zachowanie
Recovery Mode options.recover zamiast normalnej ekstrakcji uruchamia ścieżkę _recover(), która szuka fragmentów wiadomości usuniętych lub poza normalnym indeksem
Safe Mode options.safe_mode generuje nazwy plików EML bezpieczne dla każdej lokalizacji (tylko znaki ASCII + podkreślenie), przydatne gdy docelowy system plików nie obsługuje Unicode
Delete Deleted options.delete_deleted usuwa z folderu docelowego pliki EML których nie ma już w DBX; domyślnie (odznaczone) takie pliki są przenoszone do podfolderu deleted zamiast kasowania
Ignore Empty options.ignore0 pomija wiadomości z offset == 0, które oznaczają zazwyczaj uszkodzone lub nieistniejące wpisy indeksu

Cykl życia ekstrakcji w GUI

sequenceDiagram participant U as Uzytkownik participant MW as MainWindow participant ET as ExtractionThread participant EP as Extraction/dbxread participant FS as System plikow U->>MW: klik Extract MW->>MW: walidacja pol / blokada przycisku MW->>ET: CreateThread(ExtractionThread) ET->>EP: _get_files → _undbx loop EP->>FS: MapViewOfFile (read-only) EP-->>MW: PostMessage(WM_PROGRESS_UPDATE, pct) EP->>FS: zapis .eml (watkow N) EP-->>MW: PostMessage(WM_APPEND_LOG, text) ET-->>MW: PostMessage(WM_EXTRACTION_DONE, 0/1) MW->>MW: odblokowanie przycisku / SetTimer → ukrycie paska

Postęp jest raportowany atomowo: InterlockedIncrement na liczniku done w każdym wątku roboczym, co 32 wiadomości lub po zakończeniu całości — PostMessage(WM_PROGRESS_UPDATE, pct). Wątek UI aktualizuje pasek i status bar bez żadnej synchronizacji blokującej.

Theming i dark mode

Program używa niedokumentowanego API uxtheme.dll (zestaw undocumented exports przez wskaźnik indeksowy) do włączenia ciemnego motywu na Windows 10+. Na Windows 11 (build 22000+) aktywowany jest efekt MICA (DWM_SYSTEMBACKDROP_TYPE = DWMSBT_MAINWINDOW), który nadaje tłu przezroczystą teksturę systemu.

flowchart LR WM_CREATE --> InitDarkMode InitDarkMode --> DetectDarkMode DetectDarkMode --> BuildPalette BuildPalette --> ApplyMica WM_SETTINGCHANGE -->|ImmersiveColorSet| DetectDarkMode

Paleta Palette zawiera: bg, surface, input, text, textDim, accent, accentHover, accentPress, border. Zmiana motywu systemu w czasie działania programu (WM_SETTINGCHANGE) przebudowuje paletę i odświeża okno bez restartu.

Przyciski Extract i Browse... są rysowane jako owner-draw (BS_OWNERDRAW) z zaokrąglonymi narożnikami i pełną obsługą stanów Hover/Press. Ramka grupy Extraction Options rysowana jest ręcznie w WM_PAINT rodzica (zamiast BS_GROUPBOX child window), co eliminuje artefakty ghost-border przy live resize z WS_CLIPCHILDREN.

Resize okna używa BeginDeferWindowPos / EndDeferWindowPos z WM_SETREDRAW FALSE/TRUE + RDW_ALLCHILDREN | RDW_UPDATENOW — eliminuje flicker i ghost pixels przy płynnym przeciąganiu krawędzi.


Tryb CLI

Gdy wWinMain wykryje argumenty wiersza poleceń (argc > 1), wywołuje RunCLI() zamiast RunGUI().

Uruchomienie

DBXExtractor.exe [OPTIONS] <DBX-FOLDER | DBX-FILE> [OUTPUT-FOLDER]

Jeśli OUTPUT-FOLDER jest pominięty, jako folder wyjściowy używany jest bieżący katalog roboczy (.).

Opcje

Opcja Opis
-h, --help wyświetl pomoc i zakończ
-V, --version wyświetl wersję i zakończ
-v N, --verbosity N poziom szczegółowości 0–5 (domyślnie: 3 = Info)
-r, --recover tryb odzyskiwania
-s, --safe-mode bezpieczne nazwy plików (tylko ASCII)
-D, --delete usuń pliki EML których nie ma w DBX (domyślnie: przenieś do deleted)
-i, --ignore0 ignoruj wiadomości z offset 0
-d, --debug komunikaty debugowania
-c N, --codepage N strona kodowa metadanych DBX (nadawca, temat); domyślnie: CP_ACP

Przykłady

# Wyodrębnij wszystkie wiadomości z Inbox.dbx do bieżącego folderu
DBXExtractor.exe "C:\OE\Inbox.dbx"

# Wyodrębnij Sent Items do konkretnego folderu
DBXExtractor.exe "C:\OE\Sent Items.dbx" "C:\Exported\Sent"

# Wyodrębnij cały folder skrzynki OE
DBXExtractor.exe "C:\OE" "C:\Exported"

# Polska skrzynka na angielskim Windows (CP_ACP != 1250)
DBXExtractor.exe -c 1250 "C:\OE" "C:\Exported"

# Wildcard: tylko pliki z rozszerzeniem .bak które są prawdziwymi plikami DBX
DBXExtractor.exe "C:\OE\*.bak" "C:\Exported"

Obsługa konsoli

Ponieważ DBXExtractor.exe jest aplikacją GUI (/SUBSYSTEM:WINDOWS), nie ma konsoli z urzędu. SetupConsole() dołącza się do konsoli procesu nadrzędnego (AttachConsole(ATTACH_PARENT_PROCESS)), a jeśli jej brak — tworzy nową (AllocConsole()). Następnie strumienie stdout/stderr są przekierowane przez _open_osfhandle + freopen_s("CONOUT$").

SetConsoleOutputCP(CP_UTF8) ustawia UTF-8 dla całego wyjścia — ścieżki z polskimi znakami są wyświetlane poprawnie niezależnie od systemowej strony kodowej.

SendEnterToConsole() wysyła VK_RETURN do bufora wejściowego konsoli, co automatycznie odpowiada na ewentualny prompt „Press any key…" po wyświetleniu helpu, gdy nie było konsoli rodzica.


Format pliku DBX

Sygnatura

Każdy plik DBX zaczyna się od 16-bajtowej sygnatury weryfikowanej przez _dbx_has_signature():

/* Outlook Express 5/6 email store */
sig[0] == 0xFE12ADCF &&
(sig[1] == 0x6F74FDC5 || sig[1] == 0x6F74FDC6) &&
sig[2] == 0x11D1E366 &&
sig[3] == 0xC0004E9A

/* Outlook Express 4 (stary format JMF) */
sig[0] == 0x36464D4A && sig[1] == 0x00010003

Sprawdzenie sygnatury jest wykonywane przed dodaniem pliku do listy przetwarzania. Dzięki temu wildcardy (np. *.bak) działają poprawnie — niebędące DBX pliki są po cichu pomijane.

Typy pliku DBX

Typ Stała Opis
DBX_TYPE_EMAIL główny typ folder wiadomości (Inbox, Sent, itp.) — jedyny przetwarzany normalnie
DBX_TYPE_FOLDER folder meta plik indeksu folderów (Folders.dbx) — pomijany
DBX_TYPE_OE4 legacy stary format OE4
DBX_TYPE_UNKNOWN nieznany pominięty

Struktura logiczna

Plik DBX jest plikiem binarnym z listą indeksów (dbx_info_t) wskazujących na offsety fragmentów wiadomości w obrębie pliku. Każdy indeks dbx_info_t przechowuje metadane wiadomości:

Pole Typ Opis
filename char * nazwa pliku wyjściowego EML (generowana przez parser)
offset int offset bajtu początku danych wiadomości w pliku DBX
message_size uint rozmiar wiadomości w bajtach wg indeksu
send_create_time filetime_t czas wysłania (FILETIME Windows)
receive_create_time filetime_t czas odebrania
subject char * temat (UTF-8 po dekodowaniu)
sender_name char * nazwa nadawcy
sender_address char * adres e-mail nadawcy
flags uint flagi statusu (przeczytana, odpowiedziana, itp.)
valid dbx_mask_t bitmapa pól zwalidowanych przez parser

Pole valid zawiera OR flag DBX_MASK_* — parser ustawia odpowiedni bit tylko dla pól, które udało mu się poprawnie odczytać z pliku.

Memory-mapped I/O

Cały plik DBX jest mapowany do pamięci jednym wywołaniem MapViewOfFile przy dbx_open(). Odczyty to czyste operacje na wskaźnikach bez fseek/fread — system operacyjny ładuje strony na żądanie (demand paging). Szczegóły w sekcji Memory-mapped I/O.


Format pliku EML

Plik EML to surowy RFC 2822 — dokładnie ta sama sekwencja bajtów, która była przechowywana wewnątrz DBX. DBXExtractor nie konwertuje ani nie modyfikuje treści wiadomości; zapisuje ją wprost do pliku bez żadnych transformacji.

Microsoft Outlook otwiera pliki .eml bezpośrednio przez dwuklik lub przez File → Open. Thunderbird, Windows Mail i każdy klient zgodny z RFC 2822 również.

Nazewnictwo plików EML

Parser generuje nazwę pliku EML na podstawie metadanych wiadomości z indeksu DBX (temat, timestamp, nadawca). W trybie --safe-mode nazwa zawiera wyłącznie znaki ASCII + podkreślenie, co zapewnia kompatybilność z systemami plików FAT32 i starszymi narzędziami.

Pliki EML już istniejące w folderze docelowym są porównywane po rozmiarze z rozmiarem z indeksu DBX. Plik jest nadpisywany tylko jeśli rozmiar się różni (DBX_EXTRACT_MAYBE). Nowe wiadomości są zapisywane bezwarunkowo (DBX_EXTRACT_FORCE). Pliki EML bez odpowiadającej im wiadomości w DBX trafiają do podfolderu deleted (lub są kasowane przy --delete).


Architektura

Warstwy

flowchart TD APP[Main.cpp] --> GUI[GUI RunGUI] APP --> CLI[CLI RunCLI] GUI --> MW[MainWindow] MW --> CTR[Controls] MW --> THM[Theme] MW --> RND[Rendering] MW --> HLP[HelpDialog] CTR --> EXT[Extraction] CLI --> EXT EXT --> DBR[dbxread.c] EXT --> SYS[dbxsys.c] DBR --> PRG[dbxprogress.c] DBR --> EML[emlread.c] DBR --> SYS

Podział C / C++

Rdzeń parsowania DBX (dbxread.c, dbxsys.c, dbxprogress.c, emlread.c) jest napisany w czystym C z blokami #ifdef __cplusplus extern "C" — zachowuje kompatybilność z oryginalnym kodem undbx/DbxConv i umożliwia kompilację zarówno jako C jak i C++. Warstwa GUI i CLI jest w C++23.


Algorytm ekstrakcji

Funkcja _undbx() w Extraction.cpp jest głównym punktem wejścia dla jednego pliku DBX.

flowchart TD START[_undbx] --> OPEN[dbx_open: mmap pliku] OPEN --> CHECK{DBX_TYPE_EMAIL?} CHECK -->|Nie| SKIP[pominięcie] CHECK -->|Tak| MKDIR[sys_mkdir: folder docelowy] MKDIR --> GLOB[sys_glob: lista .eml w folderze] GLOB --> PHASE1[Faza 1: decyzja na każdą wiadomość] PHASE1 --> SORT[qsort po offset: optymalizacja mmap] SORT --> PHASE2[Faza 2: równoległy zapis .eml] PHASE2 --> CLOSE[dbx_close: UnmapViewOfFile]

Faza 1 — decyzja (serial, bez I/O)

Parser porównuje posortowaną listę wiadomości z indeksu DBX z posortowaną listą istniejących plików EML w folderze docelowym (merge join dwóch posortowanych list). Wynikiem jest przypisanie jednej z trzech decyzji do każdego dbx_info_t.extract:

Decyzja Znaczenie
DBX_EXTRACT_FORCE nowa wiadomość — zapisz bezwarunkowo
DBX_EXTRACT_MAYBE wiadomość istnieje — zapisz jeśli rozmiar się zmienił
DBX_EXTRACT_IGNORE wiadomość z offset 0 i --ignore0 aktywny

Pliki EML bez odpowiednika w indeksu DBX są obsługiwane przez sys_move (do deleted/) lub sys_delete zgodnie z flagą delete_deleted.

Faza 2 — równoległy zapis

Przed fazą 2 lista dbx->info jest sortowana po offset (rosnąco) przez qsort. Sortowanie po offsetie pliku gwarantuje sekwencyjny dostęp do zmapowanej pamięci wewnątrz każdego wątku — strony DBX są odczytywane w kolejności, co minimalizuje page faults i maksymalizuje efektywność cache TLB.

Szczegóły puli wątków w sekcji Równoległy zapis plików EML.


Równoległy zapis plików EML

Funkcja _run_parallel_save() dzieli tablicę wiadomości na N ciągłych plasterków i uruchamia N wątków Win32, gdzie N = liczba procesorów logicznych (GetSystemInfo().dwNumberOfProcessors), ograniczona do 64 i do liczby wiadomości.

int nthreads = (int)si.dwNumberOfProcessors;
if (nthreads > 64)                nthreads = 64;
if (nthreads > dbx->message_count) nthreads = dbx->message_count;

Każdy wątek przetwarza wyłącznie swój plasterek — nie ma współdzielonego stanu mutowalnego. dbx->_base (zmapowany widok pliku) jest const i tylko do odczytu. Każdy plik EML ma unikalną ścieżkę — brak wyścigów na plikach wyjściowych.

Struktura współdzielonych liczników atomowych

typedef struct {
    volatile LONG done;   /* przetworzone wiadomości (dla % postępu) */
    volatile LONG saved;  /* zapisane pliki EML                       */
    volatile LONG errors; /* błędy zapisu                             */
    int           total;  /* dbx->message_count (stała)               */
} ParallelCtrs;

InterlockedIncrement gwarantuje atomowość inkrementacji bez mutexów. PostMessage(WM_PROGRESS_UPDATE) co 32 wiadomości lub przy zakończeniu — wątek UI aktualizuje pasek bez blokowania wątków roboczych.

Fallback seryjny

Jeśli calloc na tablicę uchwytów wątków zawiedzie (OOM), ekstrakcja odpada na fallback seryjny — pętla po wszystkich wiadomościach w głównym wątku extraction, bez utraty danych.

Absolutne ścieżki — brak sys_chdir w wątkach

Istotna zmiana względem oryginalnego undbx.c: funkcje _save_message, _set_message_time, _set_message_filetime i _filesize_abs budują absolutne ścieżki przez _build_path(dir, filename) zamiast sys_chdir(dir) + fopen(filename). sys_chdir jest operacją globalną dla procesu (zmienia CWD), co przy wywołaniu z wielu wątków jednocześnie powoduje wyścigi. Wywołanie _wfopen z absolutną ścieżką jest thread-safe.


Tryb odzyskiwania (Recovery Mode)

Gdy options.recover == 1, zamiast _extract() wywołana jest _recover(). Tryb odzyskiwania skanuje zawartość pliku DBX w poszukiwaniu łańcuchów fragmentów (dbx_chains_t) — również tych oznaczonych jako usunięte — i próbuje odtworzyć z nich kompletne wiadomości.

flowchart LR DBX[Plik DBX] --> SCAN[Skanowanie lancuchow fragmentow] SCAN --> LIVE[Lancuchy aktywne] SCAN --> DEL[Lancuchy usuniete deleted=1] LIVE --> SAVE[Zapis do eml_dir] DEL --> SAVD[Zapis do eml_dir/deleted]

Każdy łańcuch (dbx_chains_t) zawiera listę obiektów dbx_fragment_t z polami offset, offset_next, offset_prev, size. Parser scala fragmenty w kolejności łańcucha i wywołuje dbx_recover_message(), która zwraca surowy bufor wiadomości wraz z timestampem i wygenerowaną nazwą pliku.

Wiadomości odzyskane z łańcuchów usuniętych (scan[i].deleted == 1) trafiają do podfolderu deleted wewnątrz folderu wyjściowego. Wiadomości aktywne trafiają bezpośrednio do głównego folderu EML.


Wykrywanie kodowania — RFC 2047 i strona kodowa

Problem

Outlook Express zapisywał metadane wiadomości (temat, nadawca) w systemowej stronie kodowej ANSI maszyny, która utworzyła plik DBX. Polska wersja OE używała CP 1250. Otwarcie takiego pliku na angielskim Windows (CP_ACP = 1252) daje przekłamania znaków.

Autodetekcja

Funkcja _dbx_detect_codepage() (wywoływana na początku dbx_open()) skanuje pierwsze DBX_DETECT_SCAN_BYTES bajtów zmapowanego pliku w poszukiwaniu nagłówków RFC 2047 w postaci =?charset?...?=. Rozpoznana nazwa zestawu znaków (np. iso-8859-2, windows-1250) jest mapowana do numeru strony kodowej Windows przez sys_charset_to_codepage(). Wynik jest zapisywany w dbx->detected_codepage.

=?iso-8859-2?Q?Za=BF=F3=B3cenie?=  →  CP 28592
=?windows-1250?B?...?=             →  CP 1250

Pierwszeństwo stron kodowych

options->metadata_codepage  (--codepage N, gdy użytkownik podał jawnie)
       ↓ jeśli 0
dbx->detected_codepage      (autodetekcja z RFC2047 w zawartości pliku)
       ↓ jeśli 0
CP_ACP                       (systemowa ANSI, fallback)

Konwersja do UTF-8

Wszystkie ciągi metadanych odczytane z DBX są konwertowane do UTF-8 przez _cp_to_utf8():

MultiByteToWideChar(codepage, 0, bytes, len, wide, wlen)  → wide
WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, ulen, …)  → utf8

Analogicznie emlread.c dekoduje nagłówki RFC 2047 (=?charset?Q?...?= i =?charset?B?...?=) w samej treści EML — quoted-printable i Base64 — przez _eml_parse_encoded_word() z konwersją do UTF-8.


Memory-mapped I/O

Pliki DBX mogą być duże (do ~2 GB — ponad 2 GB jest traktowane jako uszkodzenie z ostrzeżeniem). Oryginalny undbx.c używał fseek/fread. Implementacja DBXExtractor zastępuje to mapowaniem pamięci:

dbx->_hFile    = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, …);
dbx->_hMapping = CreateFileMappingW(dbx->_hFile, NULL, PAGE_READONLY, 0, 0, NULL);
dbx->_base     = MapViewOfFile(dbx->_hMapping, FILE_MAP_READ, 0, 0, 0);

Wszystkie odczyty to operacje na wskaźnikach (memcpy ze sprawdzeniem granic) przez inline helpery _mr_i32, _mr_i16, _mr_i64, _mr_buf, _mr_u8. Bounds checking gwarantuje, że odczyt poza końcem pliku zwraca 0 / pustą pamięć zamiast segfaultu.

Korzyści dla wydajności:

  • System operacyjny ładuje strony fizyczne na żądanie (demand paging) — maszyny z mniej RAM niż rozmiar pliku działają poprawnie.
  • Po posortowaniu wiadomości po offset dostęp do fragmentów w każdym wątku jest sekwencyjny — prefetcher CPU i TLB pracują optymalnie.
  • Plik jest otwierany z FILE_SHARE_READ — inne procesy mogą czytać DBX jednocześnie.

Przy dbx_close() mapowanie jest zwalniane przez UnmapViewOfFile + CloseHandle(_hMapping) + CloseHandle(_hFile).

Na systemach nie-Windows (Linux/macOS, ścieżka #else w dbxread.c) plik jest wczytywany do bufora przez malloc + fread — semantyka kodu pozostaje identyczna.


Obsługa plików i UTF-8

Wszystkie operacje na plikach i katalogach w dbxsys.c (Windows path) przechodzą przez wewnętrzną konwersję UTF-8 → UTF-16 przed przekazaniem do Win32 API:

static wchar_t *utf8_to_wide(const char *utf8) {
    int n = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
    wchar_t *ws = malloc(n * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, utf8, -1, ws, n);
    return ws;
}

Funkcje sys, które korzystają z tej konwersji:

Funkcja Win32 API
sys_fopen(path, mode) _wfopen(wide, wmode)
sys_glob(parent, pattern, n) FindFirstFileW / FindNextFileW
sys_mkdir(parent, dir) CreateDirectoryW
sys_chdir(dir) SetCurrentDirectoryW
sys_delete(parent, filename) DeleteFileW
sys_move(parent, file, dest) MoveFileW
sys_set_filetime(path, ft) CreateFileW + SetFileTime

Wyjście do konsoli w RunCLI(): SetConsoleOutputCP(CP_UTF8) + argumenty konwertowane WideCharToMultiByte(CP_UTF8) — ścieżki z polskimi znakami wyświetlają się poprawnie w Windows Terminal i PowerShell.


Parsowanie indeksu DBX

Struktura B-drzewa indeksu

DBX przechowuje listę wiadomości w strukturze B-drzewa. Główny wskaźnik do korzenia drzewa indeksów leży pod stałym offsetem INDEX_POINTER (0xE4) w nagłówku pliku. Funkcja _dbx_read_indexes() rekurencyjnie odczytuje węzły drzewa przez _dbx_read_index().

flowchart TD HDR[Naglowek DBX: offset 0xE4] --> ROOT[Korzen B-drzewa] ROOT --> N1[Wezel 1] ROOT --> N2[Wezel 2] N1 --> L1[Lisc: info_ptr 1] N1 --> L2[Lisc: info_ptr 2] N2 --> L3[Lisc: info_ptr 3] L1 --> INFO1[dbx_info_t: index, offset, filename, metadane] L2 --> INFO2[dbx_info_t] L3 --> INFO3[dbx_info_t]

Każdy węzeł indeksu zawiera tablicę wskaźników do rekordów dbx_info_t. Pojemność tablicy dbx->info jest zarządzana dynamicznie przez realloc z krokiem ptr_count per węzeł — dbx->message_count rośnie w miarę przechodzenia drzewa.

Parsowanie pól rekordu info

Każdy rekord dbx_info_t w pliku zawiera liczbę pól (count) zakodowaną w bitach 16–23 słowa na index+8. Kolejne pola leżą od index+12 w krokach po 4 bajty. Każde pole jest zakodowaną parą (type, value):

wartość 32-bit = (type:8)(value:24)
bit 7 type = 1  →  value jest wartością bezpośrednią (inline storage)
bit 7 type = 0  →  value jest offsetem do bloku danych extra

Numery typów rozpoznawane przez parser:

Typ (hex) Pole dbx_info_t Format
0x00 message_index int
0x01 flags int (bitmapa statusu OE)
0x02 send_create_time FILETIME (8B)
0x03 body_lines int
0x04 message_address int
0x05 original_subject string
0x06 save_time FILETIME
0x07 message_id string
0x08 subject string
0x09 sender_address_and_name string
0x0A message_id_replied_to string
0x0C server string
0x0D sender_name string
0x0E sender_address string
0x10 message_priority int
0x11 message_size int
0x12 receive_create_time FILETIME
0x13 receiver_name string
0x14 receiver_address string
0x1A account_name string
0x1B account_registry_key string

Pole valid jest bitmapą flag DBX_MASK_* — parser ustawia bit tylko dla pól, które faktycznie odczytał. Brak bitu DBX_MASK_MSGSIZE oznacza, że _maybe_save_message nie może polegać na porównaniu rozmiaru i musi zawsze wykonać odczyt blokowy.

Rekonstrukcja wiadomości (łańcuch bloków)

Dane wiadomości przechowywane są w pliku DBX jako łańcuch bloków. Każdy blok ma:

offset bloku + 8   →  int16: rozmiar payload (bsz, max 0x200 = 512 B)
offset bloku + 12  →  int32: offset następnego bloku (0 = koniec łańcucha)
offset bloku + 16  →  bsz bajtów payload

dbx_message() przechodzi łańcuch dwukrotnie (two-pass):

  1. Pass 1 — liczy sumę bsz w całym łańcuchu → total; jednorazowy malloc(total+1).
  2. Pass 2 — kopiuje payload każdego bloku do bufora przez _mr_buf (bounds-checked memcpy z widoku mmap).

Blok z bsz <= 0 lub bsz > 0x200 jest sygnałem uszkodzenia — emitowane ostrzeżenie i przerwanie łańcucha. Wynikowy bufor kończy się \0 i jest zwracany do _save_messagefwrite.

Nazewnictwo pliku EML

Dwa tryby generowania nazwy w _dbx_set_filename():

  • Tryb normalny — na podstawie timestampu i nadawcy/tematu z metadanych; może zawierać znaki Unicode.
  • Safe mode (--safe-mode) — wyłącznie %08X.eml gdzie hex to offset wiadomości w pliku DBX; gwarantuje unikalność i pełną kompatybilność ASCII.

Parsowanie RFC 2822 / RFC 2047 w emlread.c

emlread.c implementuje parser nagłówków RFC 2822 potrzebnych do wyekstrahowania metadanych wyświetlanych w logu i potrzebnych do nazewnictwa pliku EML (subject, from, to, timestamp). Nie jest pełnym parserem MIME — obsługuje tylko podzbiór wystarczający do tych potrzeb.

Quoted-Printable i Base64 (RFC 2047)

Nagłówki z kodowaniem RFC 2047 mają postać:

=?charset?encoding?encoded_text?=

gdzie encoding to Q (Quoted-Printable) lub B (Base64). Parser _eml_parse_encoded_word() rozpoznaje oba warianty i konwertuje wynik do UTF-8 przez _cp_to_utf8_eml(bytes, len, codepage):

=?iso-8859-2?Q?Za=BF=F3=B3cenie?=
    ↓ _eml_decode_qp → raw bytes w CP 28592
    ↓ _cp_to_utf8_eml(CP 28592)
    → "Zażółcenie" (UTF-8)

=?windows-1250?B?emHFvMOzxYNjaWU=?=
    ↓ _eml_decode_base64 → raw bytes w CP 1250
    ↓ _cp_to_utf8_eml(CP 1250)
    → "zażółcie" (UTF-8)

Mapowanie nazwy zestawu znaków na numer strony kodowej Windows odbywa się przez sys_charset_to_codepage() z dbxsys.c — obsługuje m.in. iso-8859-1, iso-8859-2, windows-1250, windows-1252, koi8-r, utf-8 (dla UTF-8 zwraca 0 — brak konwersji).

Operacje na stringach bez alokatorów

emlread.c używa prostego zestawu pomocników _eml_str_append_n, _eml_str_append_char, _eml_str_append_range, _eml_str_free — minimalistyczny dynamiczny bufor stringów bez zewnętrznych zależności.

Timestamp z nagłówka Date

Nagłówek Date: w RFC 2822 ma postać:

Date: Mon, 21 Apr 2003 14:30:00 +0200

Parser _eml_parse_date() rozkłada go na strukturę tm i wywołuje mktime(), zwracając time_t UTC. Timestamp jest używany do ustawienia czasu modyfikacji pliku EML przez sys_set_filetime() — po ekstrakcji plik .eml ma datę odpowiadającą dacie odebrania wiadomości, nie dacie ekstrakcji.


System logowania i postępu (dbxprogress.c)

dbxprogress.c implementuje hierarchiczny system komunikatów z paskami postępu. Kluczowe pojęcia:

Stos pasków postępu

dbx_progress_push() / dbx_progress_pop() utrzymują stos dbx_progress_bar_t. Każde wywołanie push tworzy nowy pasek z podaną maksymalną liczbą kroków; pop zamyka pasek i emituje podsumowanie. Paskowanie zagłębione (np. całość pliku → każda wiadomość) daje czytelne wcięcia w logu.

Callback GUI vs. stdout

static dbx_output_fn_t g_output_fn = NULL;

void dbx_progress_set_output(dbx_output_fn_t fn);

Gdy g_output_fn jest NULL, logi trafiają na stdout/stderr (tryb CLI). Gdy jest ustawiony na GuiProgressCallback, logi trafiają przez PostMessageA(WM_APPEND_LOG) do wątku UI — nigdy bezpośrednio, zawsze asynchronicznie.

Format komunikatu postępu

Linie postępu emitowane do callbacku zaczynają się od \nN.N% [STATUS]:

\n 42.0% [OK] 00001A2B.eml
\n 42.0%                    ← bare ping (tylko aktualizacja paska, bez logu)

GuiProgressCallback parsuje prefix — jeśli po % nie ma nic, komunikat jest czystym pingiem i nie trafia do loga. Jeśli po % jest tekst, jest dołączany do g_hLogEdit.


Obsługa resize i DPI (Controls.cpp + Rendering.cpp)

Makro S(hwnd, dp)

Rendering.cpp definiuje makro S(hwnd, dp) które skaluje logiczne „punkty dialogowe" do pikseli fizycznych z uwzględnieniem DPI okna:

#define S(hwnd, dp) MulDiv(dp, GetDpiForWindow(hwnd), 96)

Wszystkie pozycje i rozmiary kontrolek są podawane w jednostkach 96-DPI i automatycznie skalowane na monitorach HiDPI. Przy WM_SIZE LayoutControls() przelicza pozycje na nowo — okno reaguje poprawnie na przeciąganie między monitorami z różnym DPI.

Atomowe przesuwanie kontrolek

LayoutControls() używa BeginDeferWindowPos / DeferWindowPos / EndDeferWindowPos zamiast pętli SetWindowPos. Wszystkie kontrolki są przesuwane w jednej transakcji DWM:

HDWP hdwp = BeginDeferWindowPos(8);
hdwp = DeferWindowPos(hdwp, hEdit, NULL, x, y, w, h, F);
// ... dla każdej kontrolki
EndDeferWindowPos(hdwp);

Połączone z WM_SETREDRAW FALSE/TRUE i RDW_ALLCHILDREN | RDW_UPDATENOW eliminuje flicker i ghost pixels przy live resize — nawet przy szybkim przeciąganiu krawędzi okna.

Combo Verbosity

Combobox IDC_COMBO_VERBOSITY jest wypełniany 6 etykietami (QuietDebug) i inicjalizowany na indeks 3 (Info (default)). Przy starcie ekstrakcji StartExtraction() odczytuje wybraną pozycję i ustawia options.verbosity = (dbx_verbosity_t)sel.


Struktura okna dialogu pomocy (HelpDialog.cpp)

ShowHelpDialog() tworzy pseudo-modalne okno (WS_POPUP | WS_CAPTION | WS_SYSMENU) z czcionką Consolas do wyrównania kolumn. Modalność jest emulowana własną pętlą GetMessageW:

EnableWindow(parent, FALSE);
MSG msg;
while (IsWindow(hdlg) && GetMessageW(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
}
EnableWindow(parent, TRUE);

IsDialogMessageW celowo nie jest używany — na tablecach z gładzikowym WM_POINTERWHEEL / WM_GESTURE błędnie interpretuje te zdarzenia jako VK_ESCAPE i zamykałby okno przy pierwszym geście. Zamiast tego WM_KEYDOWN obsługuje VK_ESCAPE i VK_RETURN jawnie w HelpWndProc.

Okno jest centrowane nad rodzicem przez GetWindowRect + SetWindowPos(SWP_NOSIZE | SWP_NOZORDER).


Globalne uchwyty kontrolek (AppGlobals)

AppGlobals.h deklaruje globalne zmienne widoczne we wszystkich modułach C++:

Zmienna Typ Opis
g_hMainWnd HWND uchwyt głównego okna — używany przez wątki do PostMessage
g_hProgress HWND pasek postępu PBS_SMOOTH
g_hLogEdit HWND wieloliniowy log ES_READONLY
g_hExtractBtn HWND przycisk Extract (owner-draw)
g_hStatusBar HWND pasek stanu z uchwytem resize
g_hFontUI HFONT Segoe UI 10pt — kontrolki UI
g_hFontMono HFONT Consolas 9pt — log i dialog pomocy
g_hbrBg HBRUSH pędzel tła okna (zgodny z paletą)
g_hbrInput HBRUSH pędzel tła pól edycji
g_isDark BOOL aktualny tryb ciemny/jasny
g_extracting BOOL flaga blokady UI podczas ekstrakcji
g_pal Palette aktywna paleta kolorów

C++23 — wybrane zastosowania

Cecha C++23 Gdzie użyta
Lambda z przechwyceniem [&] (C++11+) CreateControls: lokalne Btn, Lbl, Edit, Chk lambdy tworzące kontrolki
Lambda ToggleMenuCheck WindowProc WM_COMMAND: synchronizacja menu ↔ checkbox bez powielania kodu
Designated initializers inicjalizacja CliArgs a = {}, dbx_options_t options = {}
[[nodiscard]] _undbx(), _get_files(), dbx_message(), dbx_open()
std::atomic-like volatile LONG + InterlockedIncrement ParallelCtrs — thread-safe liczniki bez mutexów
Range-based for for (auto h : ctrls) SetF(h, g_hFontUI) w CreateControls
auto lambda capture SetF lambda w CreateControls
constexpr / inline constexpr stałe DBX_VERSION, APP_TITLE, APP_CLASS
/std:c++latest aktywny standard C++23 z __cplusplus = 202302L
bool literals CliArgs struct pola help, version, recover, safe_mode, debug, error

Skanowanie łańcuchów fragmentów (Recovery deep-dive)

W trybie odzyskiwania dbxread.c wykonuje liniowy skan całego zmapowanego pliku w poszukiwaniu bloków-fragmentów, które mogą należeć do usuniętych lub sierocych wiadomości. Algorytm jest oparty na przesuwającym się oknie 8-słów (sliding window).

Struktura fragmentu w pliku

Każdy blok danych w DBX ma 20-bajtowy nagłówek:

+0  (4B)  offset tego bloku w pliku
+4  (4B)  junk / nieużywane
+8  (2B)  rozmiar payload bsz (max 0x200)
+10 (2B)  nieużywane
+12 (4B)  offset następnego bloku (0 = koniec)
+16 (4B)  offset poprzedniego bloku
+20 …     payload bsz bajtów

Skaner przechodzi przez plik z krokiem 4 bajty, utrzymując okno ostatnich 8 odczytanych int32. Na każdej pozycji sprawdza, czy okno pasuje do sygnatury nagłówka fragmentu:

int message_fragment_found =
    header[header_start]                            /* offset bieżący */
    && header[(header_start + 2) & 7]               /* rozmiar > 0    */
    && header[(header_start + 2) & 7] <= 0x200;     /* rozmiar ≤ 512  */

int deleted_fragment_found =
    header[header_start]
    && header[(header_start + 2) & 7]
    && header[(header_start + 2) & 7] <= 0x200
    && header[(header_start + 4) & 7] == header[header_start];

Dodatkowy warunek dla usuniętych: offset_prev == offset_bieżący — wykrywa fragmenty z „okrężnym" wskaźnikiem wstecznym, charakterystycznym dla rekordów usuniętych przez OE.

Łączenie fragmentów w łańcuchy

Dla każdego rozpoznanego fragmentu _dbx_get_scan_chains() sprawdza, czy łączy się z poprzednio znalezionym fragmentem (przez offset_next lub offset_prev). Jeśli tak — jest dołączany do istniejącego łańcucha (chains->count-- — zmniejszamy liczbę niezależnych łańcuchów). Jeśli nie — tworzy nowy łańcuch.

Tablice fragmentów i łańcuchów są alokowane z krokiem 4096 elementów przez realloc — unikają częstych realokacji przy dużych uszkodzonych plikach.

Rekonstrukcja usuniętej wiadomości

dbx_recover_message() dla danego łańcucha (chain_index, msg_number) scala payload wszystkich fragmentów w kolejności łańcucha w jeden bufor. Następnie wywołuje eml_parse() na odzyskanym buforze, żeby wyekstrahować subject, to, timestamp do sformowania nazwy pliku EML.

Timestamp z eml_parse() jest konwertowany do time_t i przekazywany do _set_message_time() — plik odzyskanej wiadomości otrzymuje datę zbliżoną do oryginalnej, jeśli nagłówek Date: przetrwał w odzyskanym fragmencie.


Wildcard i selekcja plików DBX

Funkcja _get_files() obsługuje trzy scenariusze wejścia:

1. Ścieżka do katalogu

DBXExtractor.exe "C:\OE"

sys_glob(dir, "*.dbx", &n) zwraca wszystkie pliki .dbx w katalogu. Każdy plik jest przetwarzany przez _undbx niezależnie. Folder wyjściowy dla każdego DBX to podfolder o nazwie równej nazwie pliku DBX bez rozszerzenia (np. Inbox.dbxInbox/).

2. Ścieżka do pojedynczego pliku

DBXExtractor.exe "C:\OE\Inbox.dbx"

sys_glob nie zwróci wyników — num_files == 0. Funkcja wykrywa, że ścieżka wskazuje na istniejący plik przez sys_filesize_abs(*dir) != (ull)-1. Wyodrębniana jest część katalogowa (sys_dirname) jako nowy dbx_dir i bazowa nazwa pliku (sys_basename) jako jedyny element listy.

3. Wildcard z weryfikacją sygnatury

DBXExtractor.exe "C:\OE\*.bak"

strchr(*dir, '*') wykrywa wzorzec. sys_glob(dirpart, pattern, &n) zwraca wszystkie pasujące pliki. Następnie każdy plik jest weryfikowany przez _dbx_has_signature() — odczyt pierwszych 16 bajtów i sprawdzenie sygnatury. Pliki bez poprawnej sygnatury DBX są po cichu pomijane (free + usunięcie ze skompaktowanej tablicy).

int valid = 0;
for (int i = 0; i < *num_files; i++) {
    if (_dbx_has_signature(dirpart, files[i]))
        files[valid++] = files[i];
    else
        free(files[i]);
}
*num_files = valid;

Dzięki temu wildcard *.bak lub *.old działa transparentnie z archiwami DBX zmienionymi przez użytkownika.


Bezpieczeństwo i odporność na uszkodzone pliki

Bounds checking w memory-mapped I/O

Każdy dostęp do zmapowanego widoku przechodzi przez helpery z weryfikacją granic:

static inline int _mr_i32(const dbx_t *d, long off) {
    int v = 0;
    if (off >= 0 && (size_t)(off + 4) <= d->_sz)
        memcpy(&v, d->_base + off, 4);
    return v;
}

Odczyt spoza zakresu [0, _sz) zwraca 0 / zerowy bufor zamiast segfault. Pliki DBX z uszkodzonymi wskaźnikami nie powodują crashu — parser po prostu otrzymuje 0 i obsługuje go jako koniec łańcucha lub brak danych.

Kontrola rozmiaru bloku

dbx_message() sprawdza bsz <= 0 || bsz > 0x200 dla każdego bloku. Przekroczenie limitu emituje DBX_STATUS_WARNING do logu i przerywa rekonstrukcję — plik EML może być niekompletny, ale parser nie zapętla się ani nie alokuje gigantycznych buforów.

Limit rozmiaru pliku

Plik DBX o rozmiarze ≥ 2 GB (0x80000000 bajtów) jest traktowany jako uszkodzony — dbx_progress_message(DBX_STATUS_WARNING, "DBX file … is corrupted (> 2 GB)"). Ograniczenie wynika z faktu, że offsety wewnętrzne DBX są 32-bitowe (unsigned int), co przy zmapowaniu całego pliku i offsetach jako long / int32 daje maksymalny adresowalny offset ~2 GB.

Integralność ekstrakcji przy błędach zapisu

Błąd _save_message (np. brak miejsca na dysku, brak uprawnień) jest raportowany przez DBX_STATUS_ERROR i inkrementuje licznik errors — nie przerywa przetwarzania pozostałych wiadomości. Końcowe podsumowanie dbx_progress_pop zawiera liczbę błędów: %d messages saved, %d skipped, %d errors.


Synchronizacja GUI ↔ wątek ekstrakcji

Komunikaty custom (WM_USER)

#define WM_APPEND_LOG      (WM_USER + 1)  /* lParam = heap char*  — przejęty przez UI, zwolniony */
#define WM_EXTRACTION_DONE (WM_USER + 2)  /* wParam = 0:ok / 1:błędy */
#define WM_PROGRESS_UPDATE (WM_USER + 3)  /* wParam = procent 0–100 */

Wątki robocze nigdy nie dotykają kontrolek GUI bezpośrednio — tylko PostMessage do g_hMainWnd. WM_APPEND_LOG niesie wskaźnik na heap char* — wątek UI odbiera go w WM_APPEND_LOG, wywołuje AppendLog(text) i free(text). Brak ryzyka race condition na stercie: alokacja w wątku roboczym, zwolnienie w wątku UI, jeden wyraźny moment przeniesienia własności.

Stan g_extracting

Flaga g_extracting = TRUE ustawiana jest przez StartExtraction() przed CreateThread. Resetowana jest przez WM_EXTRACTION_DONE w wątku UI. W tym czasie:

  • Przycisk Extract jest wyłączony (EnableWindow(g_hExtractBtn, FALSE)) i rysuje etykietę Working…
  • Naciśnięcie VK_RETURN w WM_KEYDOWN jest ignorowane (!g_extracting)
  • WM_CLOSE pyta o potwierdzenie ("Extraction is in progress. Exit anyway?")

Ukrycie paska postępu po zakończeniu

WM_EXTRACTION_DONE ustawia pasek na 100% i uruchamia SetTimer(hwnd, 2, 600, NULL). Po 600 ms WM_TIMER wywołuje ShowWindow(g_hProgress, SW_HIDE). Dzięki temu użytkownik widzi, że pasek dobił do 100%, zanim zniknie — feedback ukończenia bez natychmiastowego znikania.


Mapa modułów

Moduł Plik Odpowiedzialność
Main Main.cpp wWinMain: dispatch GUI / CLI na podstawie argc
CLI CLI.cpp/.h SetupConsole, ParseArgs, PrintUsage, RunCLI
MainWindow MainWindow.cpp/.h WindowProc (cały message loop GUI), RunGUI
Controls Controls.cpp/.h BrowseFolder, BrowseFile, CreateControls, LayoutControls, AppendLog, StartExtraction
Extraction Extraction.cpp/.h _undbx, _extract, _recover, _run_parallel_save, ExtractionThread, _get_files
Theme Theme.cpp/.h InitDarkMode, DetectDarkMode, BuildPalette, ApplyMica; paleta Palette
Rendering Rendering.cpp/.h PaintRoundButton, CreateUiFont, makro S(hwnd, dp) DPI-aware
HelpDialog HelpDialog.cpp/.h ShowHelpDialog: pseudo-modalne okno z tekstem CLI help
AppGlobals AppGlobals.cpp/.h globalne uchwyty kontrolek: g_hMainWnd, g_hProgress, g_hLogEdit, g_hExtractBtn, g_hStatusBar, g_isDark, g_extracting
AppCommon AppCommon.h wspólne include Win32, stałe wersji, WM_USER messages
dbxread dbxread.c/.h silnik parsowania DBX: dbx_open, dbx_close, dbx_message, dbx_recover_message; memory-mapped I/O; autodetekcja kodowania
dbxsys dbxsys.c/.h PAL (Platform Abstraction Layer): sys_glob, sys_mkdir, sys_fopen, sys_set_filetime, utf8_to_wide
dbxprogress dbxprogress.c/.h system raportowania postępu i logowania; callback dbx_output_fn_t dla GUI
emlread emlread.c/.h eml_parse: ekstrakcja subject, from, to, timestamp z nagłówków RFC 2822 + dekodowanie RFC 2047
undbx undbx.c oryginalne CLI undbx (POSIX); w kompilacji Windows zastąpione przez Extraction.cpp

Drzewo źródeł

DBXExtractor/
├── src/
│   ├── Main.cpp                 wWinMain → RunGUI / RunCLI dispatch
│   ├── AppCommon.h              Win32 includes, wersja, WM_USER messages
│   ├── AppGlobals.cpp/.h        globalne uchwyty kontrolek GUI
│   ├── resource.h               IDC_ / IDR_ / IDI_ stałe zasobów
│   ├── DBXExtractor.rc             zasoby: menu, ikona, version info
│   ├── cli/
│   │   ├── CLI.cpp              SetupConsole, ParseArgs, RunCLI
│   │   └── CLI.h
│   ├── ui/
│   │   ├── MainWindow.cpp/.h    WindowProc, RunGUI
│   │   ├── Controls.cpp/.h      tworzenie / layout kontrolek, StartExtraction
│   │   ├── HelpDialog.cpp/.h    pseudo-modalne okno pomocy CLI
│   │   ├── Rendering.cpp/.h     PaintRoundButton, CreateUiFont, makro S()
│   │   └── Theme.cpp/.h         dark mode, paleta, MICA
│   ├── core/
│   │   ├── Extraction.cpp/.h    _undbx, _extract, _recover, wątek równoległy
│   │   ├── dbxread.c/.h         parser DBX, mmap, autodetekcja kodowania
│   │   ├── dbxsys.c/.h          PAL: glob, mkdir, fopen UTF-8, filetime
│   │   ├── dbxprogress.c/.h     system logowania i postępu
│   │   └── emlread.c/.h         parser RFC 2822 / RFC 2047
│   └── icon/
│       └── Icon.ico
├── DBXExtractor.vcxproj            projekt MSVC v145, C++23, x64/x86
├── DBXExtractor.vcxproj.filters    filtry Solution Explorer
├── DBXExtractor.sln                plik solution
└── build.ps1                    skrypt MSBuild PowerShell

Budowanie

Wymagania

  • Visual Studio 2022 lub 2026 z toolsetem MSVC v145
  • Windows SDK 10.0
  • PowerShell 5.1+

Kompilacja

.\build.ps1

Lub bezpośrednio przez MSBuild:

msbuild DBXExtractor.vcxproj /p:Configuration=Release /p:Platform=x64

Konfiguracje projektu

Konfiguracja Optymalizacja CRT
Debug | x64 wyłączona dynamiczny /MDd
Release | x64 MaxSpeed /O2 statyczny /MT
Debug | Win32 wyłączona dynamiczny /MDd
Release | Win32 MaxSpeed /O2 statyczny /MT

Flagi kompilatora stosowane we wszystkich konfiguracjach:

/utf-8              kodowanie źródła i wykonania = UTF-8
/W4                 wysoki poziom ostrzeżeń
/permissive-        ścisła zgodność ISO C++
/sdl                dodatkowe kontrole bezpieczeństwa
/std:c++latest      C++23
/Zc:__cplusplus     __cplusplus odzwierciedla faktyczny standard
/Zc:preprocessor    nowy preprocesor zgodny z normą

Release /MT zapewnia brak zależności od redystrybuowalnych pakietów VC++. Wynikowy DBXExtractor.exe działa na każdym Windows 10+ bez instalatorów.


Wymagania systemowe

Składnik Wymaganie
System operacyjny Windows 10 lub nowszy (MICA wymaga Win 11 build 22000+)
Architektura x64 (projekt domyślny) lub Win32
Zależności zewnętrzne brak — wyłącznie WinAPI (comctl32, dwmapi, uxtheme, shell32, comdlg32)
Pliki wejściowe pliki .dbx z Outlook Express 4, 5 lub 6
Pliki wyjściowe pliki .eml (RFC 2822) otwieralne przez Microsoft Outlook
Uprawnienia tylko do odczytu dla DBX; zapis do folderu docelowego

Poziomy szczegółowości (Verbosity)

Poziom Stała Opis
0 DBX_VERBOSITY_QUIET brak wyjścia
1 DBX_VERBOSITY_ERROR tylko błędy krytyczne
2 DBX_VERBOSITY_WARNING błędy + ostrzeżenia
3 DBX_VERBOSITY_INFO (domyślny) — informacje o ekstrakcji
4 DBX_VERBOSITY_VERBOSE szczegóły każdej wiadomości
5 DBX_VERBOSITY_DEBUG pełne debugowanie parsera

W GUI poziom wybierany jest comboboksem. W CLI flagą -v N / --verbosity N. Flaga -d / --debug jest skrótem dla poziomu 5 niezależnie od -v.


Statusy postępu

dbxprogress.c definiuje pięć statusów raportowanych przez dbx_progress_update():

Status Stała Znaczenie
DBX_STATUS_OK wiadomość zapisana pomyślnie
DBX_STATUS_ERROR błąd zapisu pliku EML
DBX_STATUS_WARNING ostrzeżenie (plik pominięty, uszkodzony wpis)
DBX_STATUS_MOVED plik EML przeniesiony do deleted/
🗑 DBX_STATUS_DELETED plik EML usunięty (--delete)

W GUI statusy trafiają do logu przez callback GuiProgressCallbackPostMessageA(WM_APPEND_LOG)AppendLog()SetWindowTextW na ES_MULTILINE. Linie zaczynające się od \nN% są parsowane jako aktualizacja paska; czyste pingowanie postępu (nic po %) nie trafia do logu — tylko aktualizuje pasek.


Pochodzenie kodu — UnDBX i DbxConv

DBXExtractor jest przepisanym i rozszerzonym potomkiem dwóch projektów open source:

Projekt Autor Rola w DBXExtractor
UnDBX Avi Rozen oryginalne CLI do ekstrakcji DBX, algorytmy _extract, _recover, sys_* PAL
DbxConv (DbxRead) Ulrich Krebs parser formatu binarnego DBX: B-drzewo indeksu, dekodowanie pól dbx_info_t, rekonstrukcja łańcuchów bloków
GNU Mailutils (podzbiór) Free Software Foundation parser RFC 2822 / RFC 2047 w emlread.c: decoded-word, QP, Base64

Kluczowe zmiany wprowadzone w DBXExtractor względem oryginałów:

  1. Memory-mapped I/O — zastąpienie fseek/fread przez MapViewOfFile + inline helpery z bounds checking. Efekt: brak fseek w kodzie parsera, dostęp przez wskaźniki, demand paging przez OS.
  2. Równoległy zapis plików EML — pula wątków Win32 (CreateThread × N CPU), InterlockedIncrement, absolutne ścieżki eliminujące sys_chdir z wątków roboczych.
  3. Autodetekcja strony kodowej — skan RFC 2047 charset w zawartości DBX przed parsowaniem metadanych; kaskada user → detected → CP_ACP.
  4. GUI WinAPI — warstwa graficzna napisana od zera: owner-draw buttons, MICA/dark mode, DPI-aware layout, live resize bez flickera.
  5. Dual-mode entry point — jeden plik wykonywalny, dwa tryby (wWinMain dispatch na argc), identyczny silnik ekstrakcji.
  6. UTF-8 end-to-end — wszystkie ścieżki i metadane w UTF-8 wewnętrznie; konwersja do UTF-16 wyłącznie na granicy Win32 API.

DBXExtractor v1.0.0.1 — WinAPI, C/C++23, zero zewnętrznych zależności.
kvc.pl | [email protected]