DBXExtractor — Ekstraktor archiwów Outlook Express

Spis treści
- Co robi program
- Tryb GUI
- Tryb CLI
- Format pliku DBX
- Format pliku EML
- Architektura
- Algorytm ekstrakcji
- Równoległy zapis plików EML
- Tryb odzyskiwania (Recovery Mode)
- Wykrywanie kodowania — RFC 2047 i strona kodowa
- Memory-mapped I/O
- Obsługa plików i UTF-8
- Mapa modułów
- Drzewo źródeł
- Budowanie
- Wymagania systemowe
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
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.
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
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.
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.
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
offsetdostę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().
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):
- Pass 1 — liczy sumę
bszw całym łańcuchu →total; jednorazowymalloc(total+1). - 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_message → fwrite.
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.emlgdzie 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 (Quiet … Debug) 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.dbx → Inbox/).
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_RETURNwWM_KEYDOWNjest ignorowane (!g_extracting) WM_CLOSEpyta 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 GuiProgressCallback → PostMessageA(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:
- Memory-mapped I/O — zastąpienie
fseek/freadprzezMapViewOfFile+ inline helpery z bounds checking. Efekt: brakfseekw kodzie parsera, dostęp przez wskaźniki, demand paging przez OS. - Równoległy zapis plików EML — pula wątków Win32 (
CreateThread× N CPU),InterlockedIncrement, absolutne ścieżki eliminującesys_chdirz wątków roboczych. - Autodetekcja strony kodowej — skan RFC 2047 charset w zawartości DBX przed parsowaniem metadanych; kaskada
user → detected → CP_ACP. - GUI WinAPI — warstwa graficzna napisana od zera: owner-draw buttons, MICA/dark mode, DPI-aware layout, live resize bez flickera.
- Dual-mode entry point — jeden plik wykonywalny, dwa tryby (
wWinMaindispatch naargc), identyczny silnik ekstrakcji. - 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]