1use binread::BinRead;
6use time::OffsetDateTime;
7
8pub const SECTOR_SIZE: usize = 512;
9pub const MFT_RECORD: u64 = 0;
10pub const ROOT_RECORD: u64 = 5;
11pub const FIRST_NORMAL_RECORD: u64 = 24;
12pub const FILE_RECORD_SIGNATURE: &[u8; 4] = b"FILE";
13pub const EPOCH_DIFFERENCE: u64 = 116_444_736_000_000_000;
14
15#[allow(unused)]
16#[repr(C, packed)]
17#[derive(Clone, Copy, BinRead)]
18pub struct BootSector {
19 pub crap_0: [u8; 11],
20 pub sector_size: u16,
21 pub sectors_per_cluster: u8,
22 pub crap_1: [u8; 26],
23 pub total_sectors: u64,
24 pub mft_lcn: u64,
25 pub mft_lcn_mirror: u64,
26 pub file_record_size_info: i8,
27 pub crap_2: [u8; 447],
28}
29
30#[repr(C, packed)]
31pub struct NtfsFileRecordHeader {
32 pub signature: [u8; 4],
34 pub update_sequence_offset: u16,
35 pub update_sequence_length: u16,
36 pub logfile_sequence_number: u64,
37 pub sequence_value: u16,
39 pub link_count: u16,
40 pub attributes_offset: u16,
41 pub flags: u16,
42 pub used_size: u32,
43 pub allocated_size: u32,
44 pub base_reference: u64,
45 pub next_attribute_id: u16,
46}
47
48#[repr(u16)]
49pub enum NtfsFileFlags {
50 InUse = 0x0001,
51 IsDirectory = 0x0002,
52}
53
54#[allow(unused)]
55#[repr(C, packed)]
56pub struct NtfsAttributeHeader {
57 pub type_id: u32,
58 pub length: u32,
59 pub is_non_resident: u8,
60 pub name_length: u8,
61 pub name_offset: u16,
62 pub flags: u16,
63 pub id: u16,
64}
65
66#[repr(C, packed)]
67pub struct NtfsResidentAttributeHeader {
68 pub attribute_header: NtfsAttributeHeader,
69 pub value_length: u32,
70 pub value_offset: u16,
71 pub indexed_flag: u8,
72}
73
74#[repr(C, packed)]
75pub struct NtfsNonResidentAttributeHeader {
76 pub attribute_header: NtfsAttributeHeader,
77 pub lowest_vcn: i64,
78 pub highest_vcn: i64,
79 pub data_runs_offset: u16,
80 pub compression_unit_exponent: u8,
81 pub reserved: [u8; 5],
82 pub allocated_size: u64,
83 pub data_size: u64,
84 pub initialized_size: u64,
85}
86
87#[repr(C, packed)]
88pub struct NtfsStandardInformation {
89 pub creation_time: u64,
90 pub modification_time: u64,
91 pub mft_record_modification_time: u64,
92 pub access_time: u64,
93 pub file_attributes: u32,
94}
95
96#[repr(u8)]
97pub enum NtfsFileNamespace {
98 Posix = 0,
99 Win32 = 1,
100 Dos = 2,
101 Win32AndDos = 3,
102}
103
104#[repr(C, packed)]
105#[derive(Copy, Clone)]
106pub struct NtfsFileNameHeader {
107 pub parent_directory_reference: u64,
108 pub crap_0: [u8; 32],
109 pub allocated_size: u64,
110 pub real_size: u64,
111 pub file_attributes: u32,
112 pub reparse_point_tag: u32,
113 pub name_length: u8,
114 pub namespace: u8,
115}
116
117#[repr(u32)]
118pub enum NtfsFileNameFlags {
119 ReadOnly = 0x0001,
120 Hidden = 0x0002,
121 System = 0x0004,
122 Archive = 0x0020,
123 Device = 0x0040,
124 Normal = 0x0080,
125 Temporary = 0x0100,
126 SparseFile = 0x0200,
127 ReparsePoint = 0x0400,
128 Compressed = 0x0800,
129 Offline = 0x1000,
130 NotContentIndexed = 0x2000,
131 Encrypted = 0x4000,
132 IsDirectory = 0x1000_0000,
133}
134
135#[repr(C, packed)]
136#[derive(Copy, Clone)]
137pub struct NtfsFileName {
138 pub header: NtfsFileNameHeader,
139 pub data: [u16; 255],
140}
141
142impl std::fmt::Display for NtfsFileName {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 let data = self.data;
145 let string = String::from_utf16_lossy(&data[..self.header.name_length as usize]);
146 write!(f, "{}", string)
147 }
148}
149
150#[repr(C, packed)]
151pub struct NtfsAttributeListEntry {
152 pub type_id: u32,
153 pub length: u16,
154 pub name_length: u8,
155 pub name_offset: u8,
156 pub starting_vcn: u64,
157 pub base_file_reference: u64,
158 pub id: u16,
159 }
161
162impl NtfsAttributeListEntry {
163 pub fn reference(&self) -> u64 {
164 self.base_file_reference & 0x0000_FFFF_FFFF_FFFF
165 }
166}
167
168impl NtfsFileName {
169 pub fn parent(&self) -> u64 {
170 self.header.parent_directory_reference & 0x0000_FFFF_FFFF_FFFF
171 }
172
173 pub fn is_readonly(&self) -> bool {
174 self.header.file_attributes & NtfsFileNameFlags::ReadOnly as u32 != 0
175 }
176
177 pub fn is_hidden(&self) -> bool {
178 self.header.file_attributes & NtfsFileNameFlags::Hidden as u32 != 0
179 }
180
181 pub fn is_system(&self) -> bool {
182 self.header.file_attributes & NtfsFileNameFlags::System as u32 != 0
183 }
184
185 pub fn is_reparse_point(&self) -> bool {
186 self.header.file_attributes & NtfsFileNameFlags::ReparsePoint as u32 != 0
187 }
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191#[repr(u32)]
192pub enum NtfsAttributeType {
193 StandardInformation = 0x10,
194 AttributeList = 0x20,
195 FileName = 0x30,
196 Data = 0x80,
197 Bitmap = 0xB0,
198 End = 0xFFFF_FFFF,
199}
200
201pub fn ntfs_to_unix_time(src: u64) -> OffsetDateTime {
202 let unix = src.saturating_sub(EPOCH_DIFFERENCE) as i128;
203 OffsetDateTime::from_unix_timestamp_nanos(unix * 100).unwrap_or(OffsetDateTime::UNIX_EPOCH)
204}