@@ -37,7 +37,6 @@ use smallvec::SmallVec;
3737use zerocopy:: FromBytes ;
3838
3939use super :: index_extension:: parse_extension_to_index;
40- use crate :: ntfs:: is_internal_windows_stream;
4140use crate :: parse:: index_helpers:: {
4241 ExtensionSnapshot , InternalStreamChain , add_child_entry, add_link_to_index,
4342 add_stream_to_index, build_internal_stream_chain, chain_links, chain_streams,
@@ -338,7 +337,10 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
338337 } ;
339338
340339 if name_len == 0 {
341- // Default stream
340+ // Default stream — mark that unnamed $DATA exists
341+ // (C++ parity: distinguishes "empty $DATA" from "no $DATA")
342+ let rec = index. get_or_create ( frs) ;
343+ rec. set_has_default_data ( ) ;
342344 default_size = size;
343345 default_allocated = allocated;
344346 } else {
@@ -351,11 +353,37 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
351353 . map ( |c| u16:: from_le_bytes ( [ c[ 0 ] , c[ 1 ] ] ) )
352354 . collect ( ) ;
353355 let stream_name = String :: from_utf16_lossy ( & name_u16) ;
354- // Filter out internal Windows streams (names starting with $)
355- // These include $DSC, $REPARSE, $EA, $EA_INFORMATION, $TXF_DATA, $OBJECT_ID
356- if !is_internal_windows_stream ( & stream_name) {
357- additional_streams. push ( ( stream_name, size, allocated) ) ;
358- }
356+
357+ // C++ parity: $BadClus:$Bad (FRS 8) uses InitializedSize
358+ // instead of DataSize/AllocatedSize to avoid counting the
359+ // entire volume size (ntfs_index_load.hpp lines 431-452).
360+ let ( size, allocated) = if frs == 8
361+ && attr_header. name_length == 4
362+ && stream_name == "$Bad"
363+ && attr_header. is_non_resident != 0
364+ {
365+ let init_size_offset = offset + 56 ;
366+ if init_size_offset + 8 <= data. len ( ) {
367+ let init_size = u64:: from_le_bytes (
368+ data[ init_size_offset..init_size_offset + 8 ]
369+ . try_into ( )
370+ . unwrap_or ( [ 0 ; 8 ] ) ,
371+ ) ;
372+ ( init_size, init_size)
373+ } else {
374+ ( 0 , 0 )
375+ }
376+ } else {
377+ ( size, allocated)
378+ } ;
379+
380+ // C++ parity: ALL named $DATA streams create regular
381+ // stream entries (counted in stream_count). Internal
382+ // ones (names starting with $) are filtered from
383+ // *output* by is_internal_windows_stream checks in the
384+ // output layer, but must be counted here to match C++
385+ // stream_count semantics for descendants.
386+ additional_streams. push ( ( stream_name, size, allocated) ) ;
359387 }
360388 }
361389 }
@@ -656,16 +684,8 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
656684
657685 record. stdinfo = std_info;
658686 record. first_stream . size = SizeInfo {
659- length : if default_size == 0 && ext. first_stream_len > 0 {
660- ext. first_stream_len
661- } else {
662- default_size
663- } ,
664- allocated : if default_allocated == 0 && ext. first_stream_alloc > 0 {
665- ext. first_stream_alloc
666- } else {
667- default_allocated
668- } ,
687+ length : default_size. saturating_add ( ext. first_stream_len ) ,
688+ allocated : default_allocated. saturating_add ( ext. first_stream_alloc ) ,
669689 } ;
670690 record. first_stream . flags = if record. stdinfo . is_directory ( ) {
671691 0
@@ -764,16 +784,8 @@ pub fn parse_record_to_index(data: &[u8], frs: u64, index: &mut crate::index::Mf
764784
765785 record. stdinfo = std_info;
766786 record. first_stream . size = SizeInfo {
767- length : if default_size == 0 && ext. first_stream_len > 0 {
768- ext. first_stream_len
769- } else {
770- default_size
771- } ,
772- allocated : if default_allocated == 0 && ext. first_stream_alloc > 0 {
773- ext. first_stream_alloc
774- } else {
775- default_allocated
776- } ,
787+ length : default_size. saturating_add ( ext. first_stream_len ) ,
788+ allocated : default_allocated. saturating_add ( ext. first_stream_alloc ) ,
777789 } ;
778790 record. first_stream . flags = if record. stdinfo . is_directory ( ) {
779791 0
0 commit comments