24namespace fs = std::filesystem;
27uint16_t
le16(
const unsigned char* p) {
28 return static_cast<uint16_t
>(p[0]) | (
static_cast<uint16_t
>(p[1]) << 8);
31uint32_t
le32(
const unsigned char* p) {
32 return static_cast<uint32_t
>(p[0]) |
33 (
static_cast<uint32_t
>(p[1]) << 8) |
34 (
static_cast<uint32_t
>(p[2]) << 16) |
35 (
static_cast<uint32_t
>(p[3]) << 24);
38uint64_t
le64(
const unsigned char* p) {
39 return static_cast<uint64_t
>(p[0]) |
40 (
static_cast<uint64_t
>(p[1]) << 8) |
41 (
static_cast<uint64_t
>(p[2]) << 16) |
42 (
static_cast<uint64_t
>(p[3]) << 24) |
43 (
static_cast<uint64_t
>(p[4]) << 32) |
44 (
static_cast<uint64_t
>(p[5]) << 40) |
45 (
static_cast<uint64_t
>(p[6]) << 48) |
46 (
static_cast<uint64_t
>(p[7]) << 56);
57 const std::vector<std::pair<uint64_t, uint64_t>>& ranges,
58 const fs::path& out_path,
59 uint64_t expected_size,
61 std::ofstream out(out_path, std::ios::binary);
63 error =
trs(
"Unable to create output file") + std::string(
": ") + out_path.string();
67 std::vector<unsigned char> buffer(1024 * 1024);
69 for (
const auto& [offset, length] : ranges) {
70 uint64_t remaining = length;
71 uint64_t cursor = offset;
72 while (remaining > 0 && written < expected_size) {
73 const uint64_t allowed = std::min<uint64_t>(remaining, expected_size - written);
74 const size_t chunk =
static_cast<size_t>(std::min<uint64_t>(buffer.size(), allowed));
75 if (!reader.
read_at(cursor, buffer.data(), chunk)) {
76 error =
trs(
"Read error while exporting recovered file");
79 out.write(
reinterpret_cast<const char*
>(buffer.data()),
static_cast<std::streamsize
>(chunk));
81 error =
trs(
"Write error while exporting recovered file");
88 if (written >= expected_size)
break;
98 const fs::path& out_root,
99 std::vector<RecoveredFileRecord>& recovered,
100 std::vector<std::string>& logs,
101 std::string& error) {
102 if (!
load_bpb(volume_offset))
return false;
103 fs::create_directories(out_root);
104 logs.push_back(
trs(
"FAT32 recovery started at offset") + std::string(
" ") + std::to_string(volume_offset));
105 std::set<uint32_t> dir_guard;
109 logs.push_back(
trs(
"FAT32 recovery completed."));
127 std::array<unsigned char, 512> b{};
128 if (!
reader_.
read_at(volume_offset, b.data(), b.size()))
return false;
129 if (std::string(
reinterpret_cast<const char*
>(b.data() + 82), 8) !=
"FAT32 ")
return false;
154 const uint64_t fat_entry_offset =
fat_offset_bytes_ +
static_cast<uint64_t
>(current) * 4;
155 std::array<unsigned char, 4> entry{};
156 if (!
reader_.
read_at(fat_entry_offset, entry.data(), entry.size()))
return false;
157 next =
le32(entry.data()) & 0x0FFFFFFF;
161 std::vector<uint32_t>
cluster_chain(uint32_t start,
size_t hard_limit = 1 << 20) {
162 std::vector<uint32_t> chain;
163 if (start < 2)
return chain;
164 std::set<uint32_t> seen;
165 uint32_t current = start;
166 while (current >= 2 && current < 0x0FFFFFF8 && chain.size() < hard_limit && seen.insert(current).second) {
167 chain.push_back(current);
168 uint32_t next = 0x0FFFFFFF;
170 if (next == 0x0FFFFFF7 || next == 0)
break;
183 static const int positions[] = {1,3,5,7,9,14,16,18,20,22,24,28,30};
184 std::vector<unsigned char> raw;
186 for (
int pos : positions) {
187 const uint16_t ch =
le16(entry + pos);
188 if (ch == 0x0000 || ch == 0xFFFF) {
194 raw.push_back(
static_cast<unsigned char>(ch & 0xFF));
195 raw.push_back(
static_cast<unsigned char>((ch >> 8) & 0xFF));
202 for (
int i = 0; i < 8; ++i) {
203 const unsigned char c = entry[i];
205 base.push_back(
static_cast<char>(c));
208 for (
int i = 8; i < 11; ++i) {
209 const unsigned char c = entry[i];
211 ext.push_back(
static_cast<char>(c));
213 if (!ext.empty())
return base +
"." + ext;
218 uint32_t start_cluster,
220 const fs::path& out_dir,
221 std::vector<RecoveredFileRecord>& recovered,
222 std::vector<std::string>& logs,
223 std::string& error) {
224 if (name.empty() || name ==
"." || name ==
"..")
return true;
225 fs::create_directories(out_dir);
227 if (chain.empty() && size_bytes > 0) {
228 logs.push_back(
trs(
"Skipped FAT32 file with invalid cluster chain") + std::string(
": ") + name);
232 std::vector<std::pair<uint64_t, uint64_t>> ranges;
233 for (uint32_t cluster : chain) {
238 std::string unique = out_path.string();
240 while (fs::exists(unique)) {
247 recovered.push_back({out_path.string(),
"FAT32", name, size_bytes,
"full",
"filesystem export"});
248 logs.push_back(
trs(
"Recovered FAT32 file:") + std::string(
" ") + out_path.string());
253 const fs::path& out_dir,
254 std::vector<RecoveredFileRecord>& recovered,
255 std::vector<std::string>& logs,
257 std::set<uint32_t>& dir_guard,
259 if (depth > 32)
return true;
260 if (!dir_guard.insert(dir_cluster).second)
return true;
263 std::vector<unsigned char> cluster_buf;
264 std::vector<std::string> lfn_parts;
268 std::vector<std::string> deleted_lfn_parts;
270 for (uint32_t cluster : chain) {
272 for (
size_t off = 0; off + 32 <= cluster_buf.size(); off += 32) {
273 const unsigned char* e = cluster_buf.data() + off;
274 if (e[0] == 0x00)
return true;
276 const unsigned char attr = e[11];
295 if ((attr & 0x08) != 0) { deleted_lfn_parts.clear();
continue; }
299 if ((attr & 0x10) != 0) {
300 deleted_lfn_parts.clear();
306 std::string del_name;
307 if (!deleted_lfn_parts.empty()) {
308 for (
auto it = deleted_lfn_parts.rbegin();
309 it != deleted_lfn_parts.rend(); ++it) del_name += *it;
314 if (!sn.empty() &&
static_cast<unsigned char>(sn[0]) == 0xE5) {
319 deleted_lfn_parts.clear();
321 if (del_name.empty()) del_name =
"deleted_unnamed";
323 const uint32_t dh =
le16(e + 20);
324 const uint32_t dl =
le16(e + 26);
325 const uint32_t del_cluster = (dh << 16) | dl;
326 const uint32_t del_size =
le32(e + 28);
331 const fs::path deleted_dir = out_dir /
"deleted";
333 fs::create_directories(deleted_dir, ec);
334 const fs::path out_path = deleted_dir / del_name;
335 std::ofstream(out_path, std::ios::binary);
336 recovered.push_back({out_path.string(),
"FAT32",
337 (
"deleted/" + del_name), 0,
"partial",
341 if (del_cluster < 2 || del_cluster >= 0x0FFFFFF7)
continue;
347 const fs::path deleted_dir = out_dir /
"deleted";
349 fs::create_directories(deleted_dir, ec);
352 deleted_dir, recovered, logs, ex_err,
363 if ((attr & 0x08) != 0) { lfn_parts.clear();
continue; }
366 if (!lfn_parts.empty()) {
367 for (
auto it = lfn_parts.rbegin(); it != lfn_parts.rend(); ++it) name += *it;
373 if (name.empty()) name =
"unnamed";
375 const uint32_t high =
le16(e + 20);
376 const uint32_t low =
le16(e + 26);
377 const uint32_t start_cluster = (high << 16) | low;
378 const uint32_t size_bytes =
le32(e + 28);
379 const bool is_dir = (attr & 0x10) != 0;
382 if (name !=
"." && name !=
".." && start_cluster >= 2) {
383 const fs::path subdir = out_dir / name;
384 fs::create_directories(subdir);
385 if (!
recover_directory(start_cluster, subdir, recovered, logs, error, dir_guard, depth + 1)) {
390 if (!
export_file(name, start_cluster, size_bytes, out_dir, recovered, logs, error)) {
403 uint32_t start_cluster,
405 const fs::path& out_dir,
406 std::vector<RecoveredFileRecord>& recovered,
407 std::vector<std::string>& logs,
410 if (start_cluster < 2 || size_bytes == 0)
return false;
412 const uint32_t cluster_bytes =
static_cast<uint32_t
>(
cluster_size_);
413 if (cluster_bytes == 0)
return false;
414 const uint64_t need_clusters = (size_bytes + cluster_bytes - 1) / cluster_bytes;
420 std::vector<uint32_t> chain;
422 chain.reserve(
static_cast<size_t>(need_clusters));
423 for (uint64_t i = 0; i < need_clusters; ++i) {
424 chain.push_back(
static_cast<uint32_t
>(start_cluster + i));
429 if (chain.empty())
return false;
432 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
433 if (!out)
return false;
434 uint64_t written = 0;
435 std::vector<unsigned char> cb;
436 for (uint32_t cl : chain) {
437 if (written >= size_bytes)
break;
439 const uint64_t want = std::min<uint64_t>(cluster_bytes, size_bytes - written);
440 out.write(
reinterpret_cast<const char*
>(cb.data()),
441 static_cast<std::streamsize
>(want));
444 const std::string note = is_deleted
445 ?
"deleted-contiguous-guess"
446 :
"filesystem export";
447 const std::string integ = is_deleted ?
"partial" :
"full";
448 recovered.push_back({out_path.string(),
"FAT32",
449 (is_deleted ?
"deleted/" :
"") + name,
450 written, integ, note});
452 logs.push_back(
trs(
"Recovered FAT32 deleted file (best-effort, "
453 "assumes contiguous):") +
454 std::string(
" ") + out_path.string());
465 const fs::path& out_root,
466 std::vector<RecoveredFileRecord>& recovered,
467 std::vector<std::string>& logs,
468 std::string& error) {
469 if (!
load_vbr(volume_offset))
return false;
470 fs::create_directories(out_root);
471 logs.push_back(
trs(
"exFAT recovery started at offset") + std::string(
" ") + std::to_string(volume_offset));
475 std::set<uint32_t> dir_guard;
479 logs.push_back(
trs(
"exFAT recovery completed."));
516 std::array<unsigned char, 512> b{};
517 if (!
reader_.
read_at(volume_offset, b.data(), b.size()))
return false;
518 if (std::string(
reinterpret_cast<const char*
>(b.data() + 3), 8) !=
"EXFAT ")
return false;
544 const uint64_t idx =
static_cast<uint64_t
>(cluster - 2);
545 const uint64_t byte_index = idx / 8;
546 const uint8_t bit =
static_cast<uint8_t
>(idx % 8);
554 std::vector<unsigned char> cluster_buf;
555 for (uint32_t cluster : chain) {
557 for (
size_t off = 0; off + 32 <= cluster_buf.size(); off += 32) {
558 const unsigned char* e = cluster_buf.data() + off;
559 const uint8_t type = e[0];
560 if (type == 0x00)
break;
571 if (bitmap_chain.empty())
return false;
572 std::vector<std::pair<uint64_t, uint64_t>> ranges;
574 fs::path tmp = fs::temp_directory_path() / (
"exfat_bitmap_" + std::to_string(
serial_number_) +
".bin");
575 std::string local_error;
577 std::ifstream in(tmp, std::ios::binary);
578 allocation_bitmap_.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
589 const uint64_t fat_entry_offset =
fat_offset_bytes_ +
static_cast<uint64_t
>(current) * 4;
590 std::array<unsigned char, 4> entry{};
591 if (!
reader_.
read_at(fat_entry_offset, entry.data(), entry.size()))
return false;
592 next =
le32(entry.data()) & 0x0FFFFFFF;
596 std::vector<uint32_t>
cluster_chain(uint32_t start,
bool no_fat_chain, uint64_t data_length,
size_t hard_limit = 1 << 20) {
597 std::vector<uint32_t> chain;
598 if (start < 2)
return chain;
601 for (uint64_t i = 0; i < clusters_needed; ++i) {
602 const uint32_t cluster = start +
static_cast<uint32_t
>(i);
608 chain.push_back(cluster);
612 std::set<uint32_t> seen;
613 uint32_t current = start;
614 while (current >= 2 && current < 0x0FFFFFF8 && chain.size() < hard_limit && seen.insert(current).second) {
615 chain.push_back(current);
616 uint32_t next = 0x0FFFFFFF;
618 if (next == 0x0FFFFFF7 || next == 0)
break;
636 const fs::path& out_dir,
637 std::vector<RecoveredFileRecord>& recovered,
638 std::vector<std::string>& logs,
639 std::string& error) {
641 fs::create_directories(out_dir);
644 logs.push_back(
trs(
"Skipped exFAT file with invalid cluster chain") + std::string(
": ") + info.
name);
647 std::vector<std::pair<uint64_t, uint64_t>> ranges;
648 for (uint32_t cluster : chain) {
652 std::string unique = out_path.string();
654 while (fs::exists(unique)) {
659 recovered.push_back({out_path.string(),
"exFAT", info.
name, info.
data_length,
"full", info.
no_fat_chain ?
"bitmap-validated contiguous export" :
"filesystem export"});
660 logs.push_back(
trs(
"Recovered exFAT file:") + std::string(
" ") + out_path.string());
672 const fs::path& out_dir,
673 std::vector<RecoveredFileRecord>& recovered,
674 std::vector<std::string>& logs,
675 std::string& error) {
680 std::ofstream(out_path, std::ios::binary);
681 recovered.push_back({out_path.string(),
"exFAT",
682 (
"deleted/" + info.
name), 0,
"partial",
687 const uint64_t need_clusters =
689 std::vector<std::pair<uint64_t, uint64_t>> ranges;
690 ranges.reserve(
static_cast<size_t>(need_clusters));
691 for (uint64_t i = 0; i < need_clusters; ++i) {
692 const uint32_t cl = info.
first_cluster +
static_cast<uint32_t
>(i);
697 if (ranges.empty())
return true;
700 std::string unique = out_path.string();
702 while (fs::exists(unique)) {
704 + std::to_string(suffix++))).
string();
709 recovered.push_back({out_path.string(),
"exFAT",
711 "deleted-contiguous-guess"});
712 logs.push_back(
trs(
"Recovered exFAT deleted file (best-effort, "
713 "assumes contiguous):") +
714 std::string(
" ") + out_path.string());
719 const fs::path& out_dir,
720 std::vector<RecoveredFileRecord>& recovered,
721 std::vector<std::string>& logs,
723 std::set<uint32_t>& dir_guard,
725 if (depth > 32)
return true;
726 if (!dir_guard.insert(start_cluster).second)
return true;
729 std::vector<unsigned char> cluster_buf;
731 int remaining_secondary = 0;
732 std::vector<unsigned char> name_bytes;
734 auto finalize = [&](
bool force_dir_recurse) ->
bool {
735 if (!current.
valid)
return true;
742 const fs::path deleted_dir = out_dir /
"deleted";
744 fs::create_directories(deleted_dir, ec);
749 const fs::path subdir = out_dir / current.
name;
750 fs::create_directories(subdir);
754 return export_stream(current, out_dir, recovered, logs, error);
756 (void)force_dir_recurse;
760 for (uint32_t cluster : chain) {
762 for (
size_t off = 0; off + 32 <= cluster_buf.size(); off += 32) {
763 const unsigned char* e = cluster_buf.data() + off;
764 const uint8_t type = e[0];
766 return finalize(
false);
781 const bool is_file_de = (type == 0x85 || type == 0x05);
782 const bool is_stream_de = (type == 0xC0 || type == 0x40);
783 const bool is_name_de = (type == 0xC1 || type == 0x41);
784 const bool entry_deleted = (type == 0x05 || type == 0x40 ||
788 if (!finalize(
false))
return false;
790 current.
valid =
true;
793 remaining_secondary = e[1];
795 }
else if (current.
valid && is_stream_de) {
797 current.
name = std::string();
798 const uint8_t name_len = e[3];
801 name_bytes.reserve(
static_cast<size_t>(name_len) * 2);
803 --remaining_secondary;
804 }
else if (current.
valid && is_name_de) {
805 name_bytes.insert(name_bytes.end(), e + 2, e + 32);
808 --remaining_secondary;
809 if (remaining_secondary <= 0) {
810 if (!finalize(
true))
return false;
813 }
else if (current.
valid && remaining_secondary > 0) {
814 --remaining_secondary;
815 if (remaining_secondary <= 0) {
816 if (!finalize(
false))
return false;
820 if (!finalize(
false))
return false;
825 return finalize(
false);
833 const std::string& output_dir,
834 std::vector<RecoveredFileRecord>& recovered,
835 std::vector<std::string>& logs,
836 std::string& error) {
837 std::set<uint64_t> ntfs_offsets;
838 std::set<uint64_t> fat32_offsets;
839 std::set<uint64_t> exfat_offsets;
840 std::set<uint64_t> ext4_offsets;
841 std::set<uint64_t> btrfs_offsets;
842 std::set<uint64_t> xfs_offsets;
843 std::set<uint64_t> hfs_offsets;
844 for (
const auto& r : report.
results) {
845 if (r.type ==
"NTFS volume" || r.type ==
"Lost NTFS partition candidate" || r.type ==
"NTFS candidate") ntfs_offsets.insert(r.offset_bytes);
846 if (r.type ==
"FAT32 volume" || r.type ==
"Lost FAT32 partition candidate") fat32_offsets.insert(r.offset_bytes);
847 if (r.type ==
"exFAT volume" || r.type ==
"Lost exFAT partition candidate") exfat_offsets.insert(r.offset_bytes);
848 if (r.type ==
"ext4 volume" || r.type ==
"Lost ext4 partition candidate" || r.type ==
"ext4 candidate") ext4_offsets.insert(r.offset_bytes);
849 if (r.type ==
"Btrfs volume" || r.type ==
"Lost Btrfs partition candidate" || r.type ==
"Btrfs candidate") btrfs_offsets.insert(r.offset_bytes);
850 if (r.type ==
"XFS volume" || r.type ==
"Lost XFS partition candidate") xfs_offsets.insert(r.offset_bytes);
851 if (r.type ==
"HFS+ volume" || r.type ==
"HFSX volume" || r.type ==
"Lost HFS+ partition candidate") hfs_offsets.insert(r.offset_bytes);
855 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"ntfs");
856 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"fat32");
857 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"exfat");
858 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"ext4");
859 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"btrfs");
860 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"xfs");
861 fs::create_directories(fs::path(output_dir) /
"filesystem_recovered" /
"hfs");
862 }
catch (
const std::exception& ex) {
868 for (uint64_t offset : ntfs_offsets) {
870 std::string local_error;
871 if (!ntfs.
recover(offset, output_dir, recovered, logs, local_error)) {
872 logs.push_back(
trs(
"NTFS recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
876 for (uint64_t offset : fat32_offsets) {
877 Fat32Recovery fat32(reader);
878 const fs::path out_root = fs::path(output_dir) /
"filesystem_recovered" /
"fat32" / (
"offset_" + std::to_string(offset));
879 std::string local_error;
880 if (!fat32.recover(offset, out_root, recovered, logs, local_error)) {
881 logs.push_back(
trs(
"FAT32 recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
885 for (uint64_t offset : exfat_offsets) {
886 ExfatRecovery exfat(reader);
887 const fs::path out_root = fs::path(output_dir) /
"filesystem_recovered" /
"exfat" / (
"offset_" + std::to_string(offset));
888 std::string local_error;
889 if (!exfat.recover(offset, out_root, recovered, logs, local_error)) {
890 logs.push_back(
trs(
"exFAT recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
894 for (uint64_t offset : ext4_offsets) {
896 std::string local_error;
897 if (!ext4.
recover(offset, output_dir, recovered, logs, local_error)) {
898 logs.push_back(
trs(
"ext4 recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
902 for (uint64_t offset : btrfs_offsets) {
904 std::string local_error;
905 if (!btrfs.
recover(offset, output_dir, recovered, logs, local_error)) {
906 logs.push_back(
trs(
"Btrfs recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
910 for (uint64_t offset : xfs_offsets) {
912 std::string local_error;
913 if (!
xfs.recover(offset, output_dir, recovered, logs, local_error)) {
914 logs.push_back(
trs(
"XFS recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
918 for (uint64_t offset : hfs_offsets) {
920 std::string local_error;
921 if (!
hfs.recover(offset, output_dir, recovered, logs, local_error)) {
922 logs.push_back(
trs(
"HFS+ recovery skipped or failed at offset") + std::string(
" ") + std::to_string(offset) +
": " + local_error);
943 const char* env = std::getenv(
"BASTIONGUARD_CARVING");
944 if (env !=
nullptr && env[0] ==
'1') {
949 std::string carve_err;
950 const size_t before = recovered.size();
953 const size_t carved = recovered.size() - before;
954 if (!carve_err.empty()) {
955 logs.push_back(
trs(
"Carving error: ") + carve_err);
957 logs.push_back(
trs(
"Carving pass added ") +
958 std::to_string(carved) +
trs(
" file(s)"));
bool recover(uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
bool read_at(uint64_t offset, void *buffer, size_t len)
bool recover(uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
static bool recover_supported_filesystems(DiskReader &reader, const ScanReport &report, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
bool recover(uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
uint64_t cluster_heap_offset_bytes_
bool export_stream(const StreamInfo &info, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
uint64_t fat_offset_bytes_
bool next_cluster(uint32_t current, uint32_t &next)
bool load_allocation_bitmap()
uint32_t fat_length_sectors_
std::vector< uint32_t > cluster_chain(uint32_t start, bool no_fat_chain, uint64_t data_length, size_t hard_limit=1<< 20)
uint64_t cluster_to_offset(uint32_t cluster) const
uint64_t volume_length_sectors_
bool load_vbr(uint64_t volume_offset)
uint64_t allocation_bitmap_length_
uint32_t allocation_bitmap_cluster_
uint64_t partition_offset_sectors_
static std::string decode_utf16_name(const std::vector< unsigned char > &bytes)
bool recover(uint64_t volume_offset, const fs::path &out_root, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
bool cluster_allocated(uint32_t cluster) const
uint8_t bytes_per_sector_shift_
bool read_cluster(uint32_t cluster, std::vector< unsigned char > &out)
uint32_t fat_offset_sectors_
uint32_t root_dir_cluster_
uint64_t sectors_per_cluster_
ExfatRecovery(DiskReader &reader)
uint8_t sectors_per_cluster_shift_
uint64_t bytes_per_sector_
bool recover_directory(uint32_t start_cluster, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error, std::set< uint32_t > &dir_guard, int depth)
std::vector< unsigned char > allocation_bitmap_
uint32_t cluster_heap_offset_sectors_
bool export_stream_deleted(const StreamInfo &info, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
uint64_t data_region_offset_
bool read_cluster(uint32_t cluster, std::vector< unsigned char > &out)
uint8_t sectors_per_cluster_
std::vector< uint32_t > cluster_chain(uint32_t start, size_t hard_limit=1<< 20)
bool next_cluster(uint32_t current, uint32_t &next)
uint64_t fat_offset_bytes_
uint32_t sectors_per_fat_
bool recover(uint64_t volume_offset, const fs::path &out_root, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
Fat32Recovery(DiskReader &reader)
bool export_file(const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
static std::string decode_lfn_part(const unsigned char *entry)
static std::string short_name(const unsigned char *entry)
uint16_t bytes_per_sector_
bool recover_directory(uint32_t dir_cluster, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error, std::set< uint32_t > &dir_guard, int depth)
uint16_t reserved_sectors_
bool load_bpb(uint64_t volume_offset)
bool export_file_to_dir(const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &, bool is_deleted)
uint64_t cluster_to_offset(uint32_t cluster) const
std::string trs(const char *msgid)
bool write_data_to_file(DiskReader &reader, const std::vector< std::pair< uint64_t, uint64_t > > &ranges, const fs::path &out_path, uint64_t expected_size, std::string &error)
std::string sanitize_path_component(const std::string &in)
uint16_t le16(const unsigned char *p)
uint32_t le32(const unsigned char *p)
uint64_t le64(const unsigned char *p)
bool run_carving(DiskReader &reader, const std::string &output_dir, const CarvingOptions &opts, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
std::string sanitize_component(const std::string &in)
std::string utf16le_to_utf8(const unsigned char *data, std::size_t byte_count)
std::vector< ScanResult > results