---
title: 'DBXExtractor — Ekstraktor archiwów Outlook Express'
url: 'https://kvc.pl/repositories/dbxextractor'
markdown: 'https://kvc.pl/repositories/dbxextractor.md'
date: '2026-08-29'
description: 'DBXExtractor: ekstraktor archiwów Outlook Express DBX do plików EML (Outlook). GUI + CLI, WinAPI + C/C++, brak zależności zewnętrznych.'
---

> DBXExtractor: ekstraktor archiwów Outlook Express DBX do plików EML (Outlook).

 [ Download DBXExtractor.zip](https://kvc.pl/repositories/dbxextractor/DBXExtractor.zip) [ Download Source Code](https://kvc.pl/repositories/dbxextractor/DBXExtractor_source_code.zip)

# DBXExtractor — Ekstraktor archiwów Outlook Express

Probably the world's fastest DBX extractor — mathematically and hardware-optimized algorithm.
1 GB database extracted in under a second, while competing tools take several minutes.

![DBX Extractor](https://kvc.pl/user/pages/04.repositories/22.dbxextractor/images/DBXExtractor.png)

**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](#co-robi-program)
- [Tryb GUI](#tryb-gui)
- [Tryb CLI](#tryb-cli)
- [Format pliku DBX](#format-pliku-dbx)
- [Format pliku EML](#format-pliku-eml)
- [Architektura](#architektura)
- [Algorytm ekstrakcji](#algorytm-ekstrakcji)
- [Równoległy zapis plików EML](#rownolegly-zapis-plikow-eml)
- [Tryb odzyskiwania (Recovery Mode)](#tryb-odzyskiwania)
- [Wykrywanie kodowania — RFC 2047 i strona kodowa](#wykrywanie-kodowania)
- [Memory-mapped I/O](#memory-mapped-io)
- [Obsługa plików i UTF-8](#obsluga-plikow-i-utf-8)
- [Mapa modułów](#mapa-modulow)
- [Drzewo źródeł](#drzewo-zrodel)
- [Budowanie](#budowanie)
- [Wymagania systemowe](#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

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

```powershell
# 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()`:

```c
/* 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](#memory-mapped-io).

---

## 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](#rownolegly-zapis-plikow-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.

```c
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

```c
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()`:

```c
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:

```c
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:

```c
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_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.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

```c
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:

```cpp
#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:

```cpp
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`:

```cpp
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:

```c
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).

```c
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:

```c
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)

```c
#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

```powershell
.\build.ps1
```

Lub bezpośrednio przez MSBuild:

```powershell
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:

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](https://kvc.pl) | <marek@kvc.pl>

### Add a comment

---

## Navigation

- Parent: [Repositories](https://kvc.pl/repositories.md)
- Previous: [UEFIExtract](https://kvc.pl/repositories/uefiextract.md)
- Next: [CVE-2026-31431 — Linux AF_ALG LPE](https://kvc.pl/repositories/cve-2026-31431.md)
