Skip to content

Releases: wesmar/NTFS_EFI

EfiNtfs & EFI Commander - Release 08.2026

Choose a tag to compare

@wesmar wesmar released this 08 Aug 16:23

📦 ARCHIVE CONTENTS (NTFS_EFI.7z — 122.8K)

No password. Plain 7-Zip archive — extract and copy the files onto a FAT32 ESP or stick.

NTFS_EFI-latest/
│
├── ntfs.efi         ⭐ NTFS read+write UEFI driver (EFI_DRIVER_BINDING_PROTOCOL)  [91.5K]
├── EC.efi           ⭐ EFI Commander — dual-panel pre-boot file manager           [160.0K]
├── ntfs_probe.efi   🧪 Self-loading test harness + tree-copy benchmark            [48.5K]
├── EC.ini           ⚙ Sample configuration for EC.efi
└── README.txt       📄 Deployment guide

ntfs.efi and EC.efi must sit in the same directory — that is how EC.efi finds the driver
(<AppDir>\ntfs.efi, overridable with NtfsDriverPath in EC.ini).
ntfs_probe.efi is stricter on purpose: it loads \ntfs.efi from the root of the volume it was booted from.


🔗 DOWNLOAD

File Size Description
NTFS_EFI.7z 122.8K Driver + file manager + test harness (no password)
Source code Full tree: EDK2 header subset, prebuilt libs, PowerShell test scripts

🚀 QUICK START

UEFI Shell

Shell> load fs0:\ntfs.efi
Shell> map -r
Shell> fs1:                       # the NTFS volume, now mounted by the driver
fs1:\> cp fs0:\patched.sys fs1:\Windows\System32\drivers\

Interactive rescue stick — no Shell needed

Copy both binaries into \EFI\Boot\ on a FAT32 stick and rename the manager to the fallback path:

\EFI\Boot\BOOTX64.EFI     <- copy of EC.efi
\EFI\Boot\ntfs.efi        <- same directory, found automatically
\EFI\Boot\EC.ini          <- optional

Boot the stick: a dual-panel file manager with NTFS write access, no operating system involved.
EC.efi loads and connects ntfs.efi itself if the firmware has not.

Quit with F10 before pulling the stick. That triggers the preallocation trim, the $Volume
dirty-flag clear and FlushBlocks. Cutting power at the panel view leaves the volume marked dirty.


🔒 WHAT CHANGED IN THIS REFRESH — 08.2026

The Master File Table and its own bitmap now grow together. Allocating a record used to stop the
moment the last existing bit in $MFT:$BITMAP was taken. On any real Windows volume that attribute
is non-resident — a 136 000-record table needs about 17 KB of bits — and the non-resident branch
simply returned EFI_VOLUME_FULL, on a volume with tens of gigabytes free. It now grows in both
forms: resident in place, non-resident inside the clusters it already owns, taking one more run only
when those are used up, with the newly exposed bytes zeroed so no stale bit hands out a record that
is in use. The growth itself takes 256 clusters at a time instead of 16, which is 1024 records for a
single mapping pair in MFT record 0, and the cluster allocator now keeps the longest free run it saw
rather than the last one — asking for a big chunk used to walk past a long run and come back with
whatever few clusters sat at the end of the range.

Measured on a live Windows 11 install with its MFT filled down to 40 free records: 6344 more files
created
, the table grown from 136 960 to 143 872 records and its bitmap from 17 120 to 17 984
bytes, chkdsk "found no problems". On a fresh volume, the same test placed 30 000 of 30 000
files
, the table growing to 30 976 records in 31 runs. What stops the growth now is the
mapping-pair budget in MFT record 0.

An index block that loses its last key leaves the tree. Deleting entries one by one empties
leaves, and the emptied block used to stay marked in $BITMAP:$I30 while still hanging off an entry
in its parent. Windows treats that as damage: chkdsk reports free space marked as allocated in the
bitmap for index $I30
plus errors on the parent entries, and repairs the directory. Unhooking is
the inverse of a split — the parent's END entry gives up its child pointer and returns to its
16-byte form, while a separator carrying a key leaves the node whole and goes back into the tree
through the ordinary insert, keeping the $FILE_NAME copy Windows checks against the file's own
record. The test battery now leaves the volume chkdsk-clean, which it never did before.

An emptied directory hands its index space back. Collapsing a directory to a resident
$INDEX_ROOT cleared every allocation bit but left $INDEX_ALLOCATION owning all its clusters, with
a mapping pair for every run of them, inside the same 1 KB record that also has to hold the index
root — so emptying a big directory made the next fill harder, not easier. The attribute is now cut
back to one block, the tail clusters return to $Bitmap, the mapping pairs are re-encoded for what
is left and $BITMAP:$I30 goes back to its eight-byte minimum. One block stays mapped on purpose:
that is what lets the next create refill the directory without allocating anything. Measured on a
directory filled with 2000 long-named files and then emptied through the driver: $INDEX_ALLOCATION
down to a single 4 KB block in one run, 88 bytes of attribute, $BITMAP:$I30 from 56 bytes to
40, chkdsk clean, and the same directory refills to 2000 of 2000.

CompactOS files compressed with LZX are readable now. WOF has two codecs. XPRESS was already
handled; a file packed with compact /c /exe:LZX came back EFI_UNSUPPORTED — and those are the
ones worth reading, since a real shell32.dll goes 7.6 MB to 3.3 MB where XPRESS manages far
less. The stream is LZX as WIM uses it, and three things separate that from the LZX a decoder
written off the CAB description produces: there is no E8-translation header, the block size is a
flag rather than a number — one bit meaning "the whole 32 KiB chunk" — and x86 call translation is
undone on the way out unconditionally, with the fixed 12000000 span. Each chunk is its own stream,
so the window, the three repeated offsets and every code length start over, and a match can never
reach behind the chunk it belongs to.

Chunk reads had to change with it. A chunk begins wherever the previous one ended, so its offset
inside the compressed stream is arbitrary, and Hyper-V's DiskIo refuses such a span outright
instead of returning a short read — which looked exactly like a corrupt chunk. Reads are widened to
whole sectors now. XPRESS never hit this because its chunks are small enough that a test file fits
in one.

Verified against four real Windows binaries packed with LZX, from 72 KB to 7.6 MB and up to 243
chunks: every one reads back byte-exact through the driver, checked by full-file checksum
against the same file read by Windows — xcopy.exe 73 728, notepad.exe 360 448, cmd.exe
344 064, shell32.dll 7 947 416 bytes.

Two smaller corrections. The index trim above releases its clusters only once the record that
stopped pointing at them is on disk; releasing first would have left the old mapping addressing
space the volume already considers free. And the $MFT record bitmap is scanned the way the
cluster bitmap already was, stepping over all-ones words instead of walking 17 KB of set bits one
at a time on every create.

All three binaries carry a version resource. A UEFI image is an ordinary PE/COFF file, so it can
hold a .rsrc section like any .sys or .dll — firmware ignores it, Windows reads it, which is
why Explorer shows a Details tab for bootmgfw.efi and now shows one for these three: author,
description, copyright, contact and version. Verified afterwards that the firmware still loads both
an application and a driver built this way.

EFI Commander grew the tools a file manager is actually used for. The panels, the viewer and the
editor were already there; what a rescue session keeps asking for is proof that a copy arrived
intact and a way to find something without knowing where it is. So: SHA-256 and CRC32 of a file,
computed in 64 KB reads with no crypto library linked in; VerifyAfterCopy in EC.ini, which
re-reads both sides after every copy and fails the operation on a mismatch; a recursive tree compare
that hashes same-sized files and can then update one side while keeping entries only the destination
has; = to light up, on both panels at once, exactly what differs by name, size or modification
time; Ctrl+Q Quick View showing the first 32 KB as text, as hex when the content looks binary, or
as a directory summary; Ctrl+F2 for the DOS attribute bits and the modification time; F7 and F3
to find bytes inside the viewer; a read-only view of BootOrder, BootNext and every Boot####
description; and running a selected image with an argument string passed as LoadOptions, the way
the Shell would. Every option in F9 → Settings is written back to EC.ini the moment it changes.

The long operations stopped looking like a hang. Hashing a tree takes minutes, and the compare
used to draw one box before it started and nothing after, with no way out. It now reports the path
it has reached and stops on Esc, redrawing every sixteenth entry so the drawing cannot outweigh
the comparison, and an interrupted update still flushes what it wrote. A copy is also sized up
before it starts — the source measured by listings alone, the destination volume asked what it has
left — and a shortfall asks rather than refuses, because cluster slack is uncounted and an overwrite
gives its bytes back.

Alt+F7 looks inside files, not only at their names. The same dialog takes text the file must
contain, matched case-insensitively as ASCII by the viewer's own engine, with files read in 64 KB
chunks that carry the tail of the previous chunk forward so a match across a boundary is still
found. The mask is what keeps it affordable, so Esc is polled per file rather than...

Read more