1use crate::{Arch, Ctx, Error, Path, PathBuf, PayloadKind, SectionKind, Variant, symlink};
2use anyhow::Context as _;
3use rayon::prelude::*;
4use std::collections::BTreeMap;
5
6#[derive(Clone)]
7pub struct SplatConfig {
8 pub include_debug_libs: bool,
9 pub include_debug_symbols: bool,
10 pub enable_symlinks: bool,
11 pub preserve_ms_arch_notation: bool,
12 pub use_winsysroot_style: bool,
13 pub output: PathBuf,
14 pub map: Option<PathBuf>,
15 pub copy: bool,
16 }
18
19pub(crate) struct SdkHeaders {
22 pub(crate) inner: BTreeMap<u64, PathBuf>,
23 pub(crate) root: PathBuf,
24}
25
26impl SdkHeaders {
27 fn new(root: PathBuf) -> Self {
28 Self {
29 inner: BTreeMap::new(),
30 root,
31 }
32 }
33
34 #[inline]
35 fn get_relative_path<'path>(&self, path: &'path Path) -> anyhow::Result<&'path Path> {
36 let mut rel = path.strip_prefix(&self.root)?;
37
38 if let Some(first) = rel.iter().next() {
41 rel = rel.strip_prefix(first)?;
42 }
43
44 Ok(rel)
45 }
46}
47
48pub(crate) struct SplatRoots {
49 pub root: PathBuf,
50 pub crt: PathBuf,
51 pub sdk: PathBuf,
52 pub vcrd: PathBuf,
53 src: PathBuf,
54}
55
56pub(crate) fn prep_splat(
57 ctx: std::sync::Arc<Ctx>,
58 root: &Path,
59 winroot: Option<&str>,
60) -> Result<SplatRoots, Error> {
61 if !root.exists() {
63 std::fs::create_dir_all(root)
64 .with_context(|| format!("unable to create splat directory {root}"))?;
65 }
66
67 let root = crate::util::canonicalize(root)?;
68
69 let (crt_root, sdk_root, vcrd_root) = if let Some(crt_version) = winroot {
70 let mut crt = root.join("VC/Tools/MSVC");
71 crt.push(crt_version);
72
73 let mut sdk = root.join("Windows Kits");
74 sdk.push("10");
75
76 let vcrd = root.join("VCR");
77
78 (crt, sdk, vcrd)
79 } else {
80 (root.join("crt"), root.join("sdk"), root.join("vcrd"))
81 };
82
83 if crt_root.exists() {
84 std::fs::remove_dir_all(&crt_root)
85 .with_context(|| format!("unable to delete existing CRT directory {crt_root}"))?;
86 }
87
88 if sdk_root.exists() {
89 std::fs::remove_dir_all(&sdk_root)
90 .with_context(|| format!("unable to delete existing SDK directory {sdk_root}"))?;
91 }
92
93 if vcrd_root.exists() {
94 std::fs::remove_dir_all(&vcrd_root)
95 .with_context(|| format!("unable to delete existing VCR directory {vcrd_root}"))?;
96 }
97
98 std::fs::create_dir_all(&crt_root)
99 .with_context(|| format!("unable to create CRT directory {crt_root}"))?;
100 std::fs::create_dir_all(&sdk_root)
101 .with_context(|| format!("unable to create SDK directory {sdk_root}"))?;
102
103 let src_root = ctx.work_dir.join("unpack");
104
105 Ok(SplatRoots {
106 root,
107 crt: crt_root,
108 sdk: sdk_root,
109 vcrd: vcrd_root,
110 src: src_root,
111 })
112}
113
114#[allow(clippy::too_many_arguments)]
115pub(crate) fn splat(
116 config: &SplatConfig,
117 roots: &SplatRoots,
118 item: &crate::WorkItem,
119 tree: &crate::unpack::FileTree,
120 map: Option<&crate::Map>,
121 sdk_version: &str,
122 vcrd_version: Option<String>,
123 arches: u32,
124 variants: u32,
125) -> Result<Option<SdkHeaders>, Error> {
126 struct Mapping<'ft> {
127 src: PathBuf,
128 target: PathBuf,
129 tree: &'ft crate::unpack::FileTree,
130 kind: PayloadKind,
131 variant: Option<Variant>,
132 section: SectionKind,
133 }
134
135 let mut src = roots.src.join(&item.payload.filename);
136
137 if !config.copy {
140 src.push(".unpack");
141 if let Err(e) = std::fs::remove_file(&src) {
142 tracing::warn!("Failed to remove {src}: {e}");
143 }
144 src.pop();
145 }
146
147 let get_tree = |src_path: &Path| -> Result<&crate::unpack::FileTree, Error> {
148 let src_path = src_path
149 .strip_prefix(&roots.src)
150 .context("incorrect src root")?;
151 let src_path = src_path
152 .strip_prefix(&item.payload.filename)
153 .context("incorrect src subdir")?;
154
155 tree.subtree(src_path)
156 .with_context(|| format!("missing expected subtree '{src_path}'"))
157 };
158
159 let push_arch = |src: &mut PathBuf, target: &mut PathBuf, arch: Arch| {
160 src.push(arch.as_ms_str());
161 target.push(if config.preserve_ms_arch_notation {
162 arch.as_ms_str()
163 } else {
164 arch.as_str()
165 });
166 };
167
168 let variant = item.payload.variant;
169 let kind = item.payload.kind;
170
171 let mappings = match kind {
172 PayloadKind::CrtHeaders | PayloadKind::AtlHeaders => {
173 src.push("include");
174 let tree = get_tree(&src)?;
175
176 vec![Mapping {
177 src,
178 target: roots.crt.join("include"),
179 tree,
180 kind,
181 variant,
182 section: SectionKind::CrtHeader,
183 }]
184 }
185 PayloadKind::AtlLibs => {
186 src.push("lib");
187 let mut target = roots.crt.join("lib");
188
189 let spectre = (variants & Variant::Spectre as u32) != 0;
190 if spectre {
191 src.push("spectre");
192 target.push("spectre");
193 }
194
195 push_arch(
196 &mut src,
197 &mut target,
198 item.payload
199 .target_arch
200 .context("ATL libs didn't specify an architecture")?,
201 );
202
203 let tree = get_tree(&src)?;
204
205 vec![Mapping {
206 src,
207 target,
208 tree,
209 kind,
210 variant,
211 section: SectionKind::CrtLib,
212 }]
213 }
214
215 PayloadKind::CrtLibs => {
216 src.push("lib");
217 let mut target = roots.crt.join("lib");
218
219 let spectre = (variants & Variant::Spectre as u32) != 0;
220
221 match item
222 .payload
223 .variant
224 .context("CRT libs didn't specify a variant")?
225 {
226 Variant::Desktop => {
227 if spectre {
228 src.push("spectre");
229 target.push("spectre");
230 }
231 }
232 Variant::OneCore => {
233 if spectre {
234 src.push("spectre");
235 target.push("spectre");
236 }
237
238 src.push("onecore");
239 target.push("onecore");
240 }
241 Variant::Store => {}
242 Variant::Spectre => unreachable!(),
243 }
244
245 push_arch(
246 &mut src,
247 &mut target,
248 item.payload
249 .target_arch
250 .context("CRT libs didn't specify an architecture")?,
251 );
252
253 let tree = get_tree(&src)?;
254
255 vec![Mapping {
256 src,
257 target,
258 tree,
259 kind,
260 variant,
261 section: SectionKind::CrtLib,
262 }]
263 }
264 PayloadKind::SdkHeaders => {
265 src.push("include");
266 let tree = get_tree(&src)?;
267
268 let target = if map.is_some() {
269 let mut inc = roots.sdk.clone();
270 inc.push("Include");
271 inc.push(sdk_version);
272 inc
273 } else {
274 let mut target = roots.sdk.join("include");
275
276 if config.use_winsysroot_style {
277 target.push(sdk_version);
278 }
279
280 target
281 };
282
283 vec![Mapping {
284 src,
285 target,
286 tree,
287 kind,
288 variant,
289 section: SectionKind::SdkHeader,
290 }]
291 }
292 PayloadKind::SdkLibs => {
293 src.push("lib/um");
294
295 let mut target = roots.sdk.join("lib");
296
297 if config.use_winsysroot_style {
298 target.push(sdk_version);
299 }
300
301 target.push("um");
302
303 push_arch(
304 &mut src,
305 &mut target,
306 item.payload
307 .target_arch
308 .context("SDK libs didn't specify an architecture")?,
309 );
310
311 let tree = get_tree(&src)?;
312
313 vec![Mapping {
314 src,
315 target,
316 tree,
317 kind,
318 variant,
319 section: SectionKind::SdkLib,
320 }]
321 }
322 PayloadKind::SdkStoreLibs => {
323 src.push("lib/um");
324
325 let mut target = roots.sdk.join("lib");
326
327 if config.use_winsysroot_style {
328 target.push(sdk_version);
329 }
330
331 target.push("um");
332
333 Arch::iter(arches)
334 .map(|arch| -> Result<Mapping<'_>, Error> {
335 let mut src = src.clone();
336 let mut target = target.clone();
337
338 push_arch(&mut src, &mut target, arch);
339
340 let tree = get_tree(&src)?;
341
342 Ok(Mapping {
343 src,
344 target,
345 tree,
346 kind,
347 variant,
348 section: SectionKind::SdkLib,
349 })
350 })
351 .collect::<Result<Vec<_>, _>>()?
352 }
353 PayloadKind::Ucrt => {
354 let inc_src = src.join("include/ucrt");
355 let tree = get_tree(&inc_src)?;
356
357 let mut target = if map.is_some() {
358 let mut inc = roots.sdk.join("Include");
359 inc.push(sdk_version);
360 inc
361 } else {
362 let mut target = roots.sdk.join("include");
363 if config.use_winsysroot_style {
364 target.push(sdk_version);
365 }
366 target
367 };
368
369 target.push("ucrt");
370
371 let mut mappings = vec![Mapping {
372 src: inc_src,
373 target,
374 tree,
375 kind,
376 variant,
377 section: SectionKind::SdkHeader,
378 }];
379
380 src.push("lib/ucrt");
381
382 let mut target = roots.sdk.join("lib");
383
384 if config.use_winsysroot_style {
385 target.push(sdk_version);
386 }
387
388 target.push("ucrt");
389
390 for arch in Arch::iter(arches) {
391 let mut src = src.clone();
392 let mut target = target.clone();
393
394 push_arch(&mut src, &mut target, arch);
395
396 let tree = get_tree(&src)?;
397
398 mappings.push(Mapping {
399 src,
400 target,
401 tree,
402 kind,
403 variant,
404 section: SectionKind::SdkLib,
405 });
406 }
407
408 mappings
409 }
410 PayloadKind::VcrDebug => {
411 if let Some(version) = vcrd_version {
412 let mut src = src.clone();
413 let mut target = roots.vcrd.clone();
414
415 src.push("SourceDir");
416 if !item.payload.filename.to_string().contains("UCRT") {
417 src.push(match item.payload.target_arch.unwrap() {
418 Arch::Aarch | Arch::X86 => "System",
419 Arch::Aarch64 | Arch::X86_64 => "System64",
420 });
421 };
422
423 target.push(version);
424 target.push("bin");
425 target.push(item.payload.target_arch.unwrap().as_str());
426
427 let tree = get_tree(&src)?;
428
429 vec![Mapping {
430 src,
431 target,
432 tree,
433 kind,
434 variant,
435 section: SectionKind::VcrDebug,
436 }]
437 } else {
438 vec![]
439 }
440 }
441 };
442
443 let mut results = Vec::new();
444
445 item.progress.reset();
446 item.progress
447 .set_length(mappings.iter().map(|map| map.tree.stats().1).sum());
448 item.progress.set_message("📦 splatting");
449
450 struct Dir<'ft> {
451 src: PathBuf,
452 tar: PathBuf,
453 tree: &'ft crate::unpack::FileTree,
454 }
455
456 if let Some(map) = map {
457 mappings
458 .into_par_iter()
459 .map(|mapping| -> Result<Option<SdkHeaders>, Error> {
460 let (prefix, section) = match mapping.section {
461 SectionKind::SdkHeader => {
462 (
467 if matches!(mapping.kind, PayloadKind::Ucrt) {
468 mapping.target.parent().unwrap().to_owned()
469 } else {
470 mapping.target.clone()
471 },
472 &map.sdk.headers,
473 )
474 }
475 SectionKind::SdkLib => (roots.sdk.join("lib"), &map.sdk.libs),
476 SectionKind::CrtHeader => (mapping.target.clone(), &map.crt.headers),
477 SectionKind::CrtLib => {
478 (
479 mapping.target.parent().unwrap().to_owned(),
482 &map.crt.libs,
483 )
484 }
485 SectionKind::VcrDebug => (roots.vcrd.clone(), &map.vcrd.libs),
486 };
487
488 let mut dir_stack = vec![Dir {
489 src: mapping.src,
490 tar: mapping.target,
491 tree: mapping.tree,
492 }];
493
494 while let Some(Dir { src, mut tar, tree }) = dir_stack.pop() {
495 let mut created_dir = false;
496
497 for (fname, size) in &tree.files {
498 item.progress.inc(*size);
501
502 tar.push(fname);
503
504 let unprefixed = tar.strip_prefix(&prefix).with_context(|| {
505 format!("invalid path {tar}: doesn't begin with prefix {prefix}")
506 })?;
507
508 if !section.filter.contains(unprefixed.as_str()) {
509 tar.pop();
510 continue;
511 }
512
513 let src_path = src.join(fname);
514
515 if !created_dir {
516 std::fs::create_dir_all(tar.parent().unwrap())
517 .with_context(|| format!("unable to create {tar}"))?;
518 created_dir = true;
519 }
520
521 if config.copy {
522 std::fs::copy(&src_path, &tar)
523 .with_context(|| format!("failed to copy {src_path} to {tar}"))?;
524 } else {
525 std::fs::rename(&src_path, &tar)
526 .with_context(|| format!("failed to move {src_path} to {tar}"))?;
527 }
528
529 if let Some(symlinks) = section.symlinks.get(unprefixed.as_str()) {
532 for sl in symlinks {
533 tar.pop();
534 tar.push(sl);
535 symlink(fname.as_str(), &tar)?;
536 }
537 }
538
539 tar.pop();
540 }
541
542 for (dir, dtree) in &tree.dirs {
543 dir_stack.push(Dir {
544 src: src.join(dir),
545 tar: tar.join(dir),
546 tree: dtree,
547 });
548 }
549 }
550
551 Ok(None)
554 })
555 .collect_into_vec(&mut results);
556 } else {
557 let include_debug_libs = config.include_debug_libs;
558 let include_debug_symbols = config.include_debug_symbols;
559 let filter_store = variants & Variant::Store as u32 == 0;
560
561 mappings
562 .into_par_iter()
563 .map(|mapping| -> Result<Option<SdkHeaders>, Error> {
564 let mut sdk_headers = (mapping.kind == PayloadKind::SdkHeaders)
565 .then(|| SdkHeaders::new(mapping.target.clone()));
566
567 let mut dir_stack = vec![Dir {
568 src: mapping.src,
569 tar: mapping.target,
570 tree: mapping.tree,
571 }];
572
573 while let Some(Dir { src, mut tar, tree }) = dir_stack.pop() {
574 std::fs::create_dir_all(&tar)
575 .with_context(|| format!("unable to create {tar}"))?;
576
577 for (fname, size) in &tree.files {
578 item.progress.inc(*size);
581
582 if !include_debug_symbols && fname.extension() == Some("pdb") {
583 tracing::debug!("skipping {fname}");
584 continue;
585 }
586
587 let fname_str = fname.as_str();
588 if !include_debug_libs
589 && (mapping.kind == PayloadKind::CrtLibs
590 || mapping.kind == PayloadKind::Ucrt)
591 && let Some(stripped) = fname_str.strip_suffix(".lib")
592 && (stripped.ends_with('d')
593 || stripped.ends_with("d_netcore")
594 || stripped
595 .strip_suffix(|c: char| c.is_ascii_digit())
596 .is_some_and(|fname| fname.ends_with('d')))
597 {
598 tracing::debug!("skipping {fname}");
599 continue;
600 }
601
602 tar.push(fname);
603
604 let src_path = src.join(fname);
605
606 if config.copy {
607 std::fs::copy(&src_path, &tar)
608 .with_context(|| format!("failed to copy {src_path} to {tar}"))?;
609 } else {
610 std::fs::rename(&src_path, &tar)
611 .with_context(|| format!("failed to move {src_path} to {tar}"))?;
612 }
613
614 let kind = mapping.kind;
615
616 let mut add_symlinks = || -> Result<(), Error> {
617 match kind {
618 PayloadKind::CrtHeaders
628 | PayloadKind::AtlHeaders
629 | PayloadKind::Ucrt
630 | PayloadKind::AtlLibs
631 | PayloadKind::VcrDebug => {}
632
633 PayloadKind::SdkHeaders => {
634 if let Some(sdk_headers) = &mut sdk_headers {
635 let rel_target_path =
636 sdk_headers.get_relative_path(&tar)?;
637
638 let rel_hash = calc_lower_hash(rel_target_path.as_str());
639
640 if sdk_headers.inner.insert(rel_hash, tar.clone()).is_some()
641 {
642 anyhow::bail!(
643 "found duplicate relative path when hashed"
644 );
645 }
646
647 if let Some(additional_name) = match fname_str {
648 "mstcpip.h" => Some("Mstcpip.h"),
650 "basetsd.h" => Some("BaseTsd.h"),
652 _ => None,
653 } {
654 tar.pop();
655 tar.push(additional_name);
656
657 symlink(fname_str, &tar)?;
658 }
659 }
660 }
661 PayloadKind::CrtLibs => {
662 if let Some(angry_lib) = match fname_str.strip_suffix(".lib") {
667 Some("libcmt") => Some("LIBCMT.lib"),
668 Some("msvcrt") => Some("MSVCRT.lib"),
669 Some("oldnames") => Some("OLDNAMES.lib"),
670 _ => None,
671 } {
672 tar.pop();
673 tar.push(angry_lib);
674
675 symlink(fname_str, &tar)?;
676 }
677 }
678 PayloadKind::SdkLibs | PayloadKind::SdkStoreLibs => {
679 if fname_str.contains(|c: char| c.is_ascii_uppercase()) {
688 tar.pop();
689 tar.push(fname_str.to_ascii_lowercase());
690
691 symlink(fname_str, &tar)?;
692 }
693
694 if let Some(additional_name) = match fname_str {
697 "kernel32.Lib" => Some("Kernel32.lib"),
698 "iphlpapi.lib" => Some("Iphlpapi.lib"),
699 _ => None,
700 } {
701 tar.pop();
702 tar.push(additional_name);
703
704 symlink(fname_str, &tar)?;
705 }
706
707 if tar.extension() == Some("lib") {
710 tar.pop();
711 tar.push(fname_str.to_ascii_uppercase());
712 tar.set_extension("lib");
713
714 symlink(fname_str, &tar)?;
715 }
716 }
717 }
718
719 Ok(())
720 };
721
722 if config.enable_symlinks {
723 add_symlinks()?;
724 }
725
726 tar.pop();
727 }
728
729 if mapping.variant == Some(Variant::Store)
734 && filter_store
735 && mapping.kind == PayloadKind::CrtLibs
736 {
737 tracing::debug!("skipping CRT subdirs");
738
739 item.progress
740 .inc(tree.dirs.iter().map(|(_, ft)| ft.stats().1).sum());
741 continue;
742 }
743
744 for (dir, dtree) in &tree.dirs {
745 dir_stack.push(Dir {
746 src: src.join(dir),
747 tar: tar.join(dir),
748 tree: dtree,
749 });
750 }
751 }
752
753 Ok(sdk_headers)
754 })
755 .collect_into_vec(&mut results);
756
757 if !config.use_winsysroot_style {
758 match kind {
759 PayloadKind::SdkLibs => {
760 let mut versioned_linkname = roots.sdk.clone();
762 versioned_linkname.push("lib");
763 versioned_linkname.push(sdk_version);
764
765 if !versioned_linkname.exists() {
768 crate::symlink_on_windows_too(".", &versioned_linkname)?;
769 }
770
771 if config.enable_symlinks {
773 let mut title_case = roots.sdk.clone();
774 title_case.push("Lib");
775 if !title_case.exists() {
776 symlink("lib", &title_case)?;
777 }
778 }
779 }
780 PayloadKind::SdkHeaders => {
781 let mut versioned_linkname = roots.sdk.clone();
783 versioned_linkname.push("include");
784 versioned_linkname.push(sdk_version);
785
786 if !versioned_linkname.exists() {
789 crate::symlink_on_windows_too(".", &versioned_linkname)?;
790 }
791
792 if config.enable_symlinks {
794 let mut title_case = roots.sdk.clone();
795 title_case.push("Include");
796 if !title_case.exists() {
797 symlink("include", &title_case)?;
798 }
799 }
800 }
801 _ => (),
802 };
803 }
804 }
805
806 item.progress.finish_with_message("📦 splatted");
807
808 let headers = results.into_iter().collect::<Result<Vec<_>, _>>()?;
809
810 Ok(headers.into_iter().find_map(|headers| headers))
811}
812
813pub(crate) fn finalize_splat(
814 ctx: &Ctx,
815 sdk_version: Option<&str>,
816 roots: &SplatRoots,
817 sdk_headers: Vec<SdkHeaders>,
818 crt_headers: Option<crate::unpack::FileTree>,
819 atl_headers: Option<crate::unpack::FileTree>,
820) -> Result<(), Error> {
821 let mut files: std::collections::HashMap<
822 _,
823 Header<'_>,
824 std::hash::BuildHasherDefault<twox_hash::XxHash64>,
825 > = Default::default();
826
827 struct Header<'root> {
828 root: &'root SdkHeaders,
829 path: PathBuf,
830 }
831
832 fn compare_hashes(existing: &Path, new: &PathBuf) -> anyhow::Result<()> {
833 use crate::util::Sha256;
834
835 let existing_hash = Sha256::digest(&std::fs::read(existing)?);
836 let new_hash = Sha256::digest(&std::fs::read(new)?);
837
838 anyhow::ensure!(
839 existing_hash == new_hash,
840 "2 files with same relative path were not equal: '{existing}' != '{new}'"
841 );
842
843 Ok(())
844 }
845
846 for hdrs in &sdk_headers {
847 for (k, v) in &hdrs.inner {
848 if let Some(existing) = files.get(k) {
849 compare_hashes(&existing.path, v)?;
852 tracing::debug!("skipped {v}, a matching path already exists");
853 } else {
854 files.insert(
855 k,
856 Header {
857 root: hdrs,
858 path: v.clone(),
859 },
860 );
861 }
862 }
863 }
864
865 let mut includes: std::collections::HashMap<
866 _,
867 _,
868 std::hash::BuildHasherDefault<twox_hash::XxHash64>,
869 > = Default::default();
870
871 includes.extend(files.values().filter_map(|fpath| {
875 fpath
876 .root
877 .get_relative_path(&fpath.path)
878 .ok()
879 .and_then(|rel_path| {
880 let rp = rel_path.as_str();
881
882 if rel_path.starts_with("gl/") {
885 return None;
886 }
887
888 rp.contains(|c: char| c.is_ascii_uppercase())
889 .then(|| (PathBuf::from(rp.to_ascii_lowercase()), true))
890 })
891 }));
892
893 let regex = regex::bytes::Regex::new(r#"#include\s+(?:"|<)([^">]+)(?:"|>)?"#).unwrap();
894
895 let pb =
896 indicatif::ProgressBar::with_draw_target(Some(files.len() as u64), ctx.draw_target.into())
897 .with_style(
898 indicatif::ProgressStyle::default_bar()
899 .template(
900 "{spinner:.green} {prefix:.bold} [{elapsed}] {wide_bar:.green} {pos}/{len}",
901 )?
902 .progress_chars("█▇▆▅▄▃▂▁ "),
903 );
904
905 pb.set_prefix("symlinks");
906 pb.set_message("🔍 SDK includes");
907
908 for file in files.values() {
911 let contents =
913 std::fs::read(&file.path).with_context(|| format!("unable to read {}", file.path))?;
914
915 for caps in regex.captures_iter(&contents) {
916 let rel_path = std::str::from_utf8(&caps[1]).with_context(|| {
917 format!(
918 "{} contained an include with non-utf8 characters",
919 file.path
920 )
921 })?;
922
923 if !includes.contains_key(Path::new(rel_path)) {
928 includes.insert(PathBuf::from(rel_path), true);
929 }
930 }
931
932 pb.inc(1);
933 }
934
935 if let Some(crt) = crt_headers
936 .as_ref()
937 .and_then(|crt| crt.subtree(Path::new("include")))
938 {
939 pb.set_message("🔍 CRT includes");
940 let cr = roots.crt.join("include");
941
942 for (path, _) in &crt.files {
943 let path = cr.join(path);
945 let contents =
946 std::fs::read(&path).with_context(|| format!("unable to read CRT {path}"))?;
947
948 for caps in regex.captures_iter(&contents) {
949 let rel_path = std::str::from_utf8(&caps[1]).with_context(|| {
950 format!("{path} contained an include with non-utf8 characters")
951 })?;
952
953 if !includes.contains_key(Path::new(rel_path)) {
954 includes.insert(PathBuf::from(rel_path), false);
955 }
956 }
957
958 pb.inc(1);
959 }
960 }
961
962 if let Some(atl) = atl_headers
963 .as_ref()
964 .and_then(|atl| atl.subtree(Path::new("include")))
965 {
966 pb.set_message("🔍 ATL includes");
967 let cr = roots.crt.join("include");
968
969 for (path, _) in &atl.files {
970 let path = cr.join(path);
972 let contents =
973 std::fs::read(&path).with_context(|| format!("unable to read ATL {path}"))?;
974
975 for caps in regex.captures_iter(&contents) {
976 let rel_path = std::str::from_utf8(&caps[1]).with_context(|| {
977 format!("{path} contained an include with non-utf8 characters")
978 })?;
979
980 if !includes.contains_key(Path::new(rel_path)) {
981 includes.insert(PathBuf::from(rel_path), false);
982 }
983 }
984
985 pb.inc(1);
986 }
987 }
988
989 pb.finish();
990
991 for (include, is_sdk) in includes {
992 let lower_hash = calc_lower_hash(include.as_str());
993
994 match files.get(&lower_hash) {
995 Some(disk_file) => match (disk_file.path.file_name(), include.file_name()) {
996 (Some(disk_name), Some(include_name)) if disk_name != include_name => {
997 let mut link = disk_file.path.clone();
998 link.pop();
999 link.push(include_name);
1000 symlink(disk_name, &link)?;
1001 }
1002 _ => {}
1003 },
1004 None => {
1005 if is_sdk {
1006 tracing::debug!("SDK include for '{include}' was not found in the SDK headers");
1007 }
1008 }
1009 }
1010 }
1011
1012 if let Some(_sdk_version) = sdk_version {
1015 } else {
1020 symlink("gl", &roots.sdk.join("include/um/GL"))?;
1021 }
1022
1023 Ok(())
1024}
1025
1026use std::hash::Hasher;
1027
1028#[inline]
1029fn calc_lower_hash(path: &str) -> u64 {
1030 let mut hasher = twox_hash::XxHash64::with_seed(0);
1031
1032 for c in path.chars().map(|c| c.to_ascii_lowercase() as u8) {
1033 hasher.write_u8(c);
1034 }
1035
1036 hasher.finish()
1037}