1use anyhow::{Context, Result};
2use std::fs::File;
3use std::io::{BufReader, Read, Seek, SeekFrom};
4use std::path::Path;
5use tracing::{debug, info};
6
7use encoding_rs::UTF_16LE;
9use quick_xml::events::Event;
10use quick_xml::Reader;
11
12#[derive(Debug)]
14struct StringPool {
15 pool: Vec<String>,
16 index: usize,
17}
18
19#[allow(dead_code)]
20impl StringPool {
21 fn new() -> Self {
22 Self {
23 pool: Vec::with_capacity(32), index: 0,
25 }
26 }
27
28 fn get_string(&mut self) -> &mut String {
29 if self.index >= self.pool.len() {
30 self.pool.push(String::with_capacity(256)); }
32 let string = &mut self.pool[self.index];
33 string.clear();
34 self.index += 1;
35 string
36 }
37
38 fn reset(&mut self) {
39 self.index = 0;
40 }
42}
43
44#[derive(Debug, Clone)]
47#[allow(dead_code)]
48pub struct WimHeader {
49 pub signature: [u8; 8],
51 pub header_size: u32,
53 pub format_version: u32,
55 pub file_flags: u32,
57 pub compressed_size: u32,
59 pub guid: [u8; 16],
61 pub segment_number: u16,
63 pub total_segments: u16,
65 pub image_count: u32,
67 pub offset_table_resource: FileResourceEntry,
69 pub xml_data_resource: FileResourceEntry,
71 pub boot_metadata_resource: FileResourceEntry,
73 pub bootable_image_index: u32,
75 pub integrity_resource: FileResourceEntry,
77}
78
79#[derive(Debug, Clone)]
82#[allow(dead_code)]
83pub struct FileResourceEntry {
84 pub size: u64,
86 pub flags: u8,
88 pub offset: u64,
90 pub original_size: u64,
92}
93
94#[derive(Debug, Clone)]
96#[allow(dead_code)]
97pub struct ResourceFlags;
98
99#[allow(dead_code)]
100impl ResourceFlags {
101 pub const FREE: u8 = 0x01; pub const METADATA: u8 = 0x02; pub const COMPRESSED: u8 = 0x04; pub const SPANNED: u8 = 0x08; }
106
107#[derive(Debug, Clone)]
109#[allow(dead_code)]
110pub struct FileFlags;
111
112#[allow(dead_code)]
113impl FileFlags {
114 pub const COMPRESSION: u32 = 0x00000002; pub const READONLY: u32 = 0x00000004; pub const SPANNED: u32 = 0x00000008; pub const RESOURCE_ONLY: u32 = 0x00000010; pub const METADATA_ONLY: u32 = 0x00000020; pub const COMPRESS_XPRESS: u32 = 0x00020000; pub const COMPRESS_LZX: u32 = 0x00040000; }
122
123#[derive(Debug, Clone)]
125#[allow(dead_code)]
126pub struct ImageInfo {
127 pub index: u32,
129 pub name: String,
131 pub description: String,
133 pub dir_count: u32,
135 pub file_count: u32,
137 pub total_bytes: u64,
139 pub creation_time: Option<u64>,
141 pub last_modification_time: Option<u64>,
143 pub version: Option<String>,
145 pub architecture: Option<String>,
147}
148
149#[allow(dead_code)]
150impl ImageInfo {
151 pub fn new_with_index(index: u32) -> Self {
153 Self {
154 index,
155 name: String::new(),
156 description: String::new(),
157 dir_count: 0,
158 file_count: 0,
159 total_bytes: 0,
160 creation_time: None,
161 last_modification_time: None,
162 version: None,
163 architecture: None,
164 }
165 }
166
167 pub fn set_field(&mut self, tag: &str, value: &str) {
169 match tag {
170 "DISPLAYNAME" => self.name = value.to_string(),
171 "DISPLAYDESCRIPTION" => self.description = value.to_string(),
172 "DIRCOUNT" => self.dir_count = value.parse().unwrap_or(0),
173 "FILECOUNT" => self.file_count = value.parse().unwrap_or(0),
174 "TOTALBYTES" => self.total_bytes = value.parse().unwrap_or(0),
175 "ARCH" => {
176 self.architecture = match value {
177 "0" => Some("x86".to_string()),
178 "9" => Some("x64".to_string()),
179 "5" => Some("ARM".to_string()),
180 "12" => Some("ARM64".to_string()),
181 _ => None,
182 };
183 }
184 _ => {} }
186 }
187
188 pub fn infer_version_and_arch(&mut self) {
190 let combined_text = format!("{} {}", self.name, self.description).to_lowercase();
191
192 if self.version.is_none() {
194 self.version = if combined_text.contains("windows 11") {
195 Some("Windows 11".to_string())
196 } else if combined_text.contains("windows 10") {
197 Some("Windows 10".to_string())
198 } else if combined_text.contains("windows server 2022") {
199 Some("Windows Server 2022".to_string())
200 } else if combined_text.contains("windows server 2019") {
201 Some("Windows Server 2019".to_string())
202 } else if combined_text.contains("windows server") {
203 Some("Windows Server".to_string())
204 } else if combined_text.contains("windows") {
205 Some("Windows".to_string())
206 } else {
207 None
208 };
209 }
210
211 if self.architecture.is_none() {
213 self.architecture = if combined_text.contains("x64") || combined_text.contains("amd64")
214 {
215 Some("x64".to_string())
216 } else if combined_text.contains("x86") {
217 Some("x86".to_string())
218 } else if combined_text.contains("arm64") {
219 Some("ARM64".to_string())
220 } else {
221 None
222 };
223 }
224 }
225}
226
227#[allow(dead_code)]
229pub struct WimParser {
230 file: BufReader<File>,
231 header: Option<WimHeader>,
232 images: Vec<ImageInfo>,
233 string_pool: StringPool,
234}
235
236#[allow(dead_code)]
237impl WimParser {
238 pub fn new<P: AsRef<Path>>(wim_path: P) -> Result<Self> {
240 let file = File::open(wim_path.as_ref())
241 .with_context(|| format!("无法打开 WIM 文件: {}", wim_path.as_ref().display()))?;
242
243 let buffered_file = BufReader::with_capacity(64 * 1024, file); debug!("创建 WIM 解析器: {}", wim_path.as_ref().display());
246
247 Ok(Self {
248 file: buffered_file,
249 header: None,
250 images: Vec::with_capacity(8), string_pool: StringPool::new(),
252 })
253 }
254
255 #[doc(hidden)]
257 #[allow(dead_code)]
258 pub fn new_for_test(file: File) -> Self {
259 Self {
260 file: BufReader::new(file),
261 header: None,
262 images: Vec::with_capacity(8),
263 string_pool: StringPool::new(),
264 }
265 }
266
267 pub fn read_header(&mut self) -> Result<&WimHeader> {
269 if self.header.is_none() {
270 debug!("开始读取 WIM 文件头");
271
272 self.file.seek(SeekFrom::Start(0))?;
274
275 let mut header_buffer = vec![0u8; 204];
277 self.file
278 .read_exact(&mut header_buffer)
279 .context("读取 WIM 文件头失败")?;
280
281 let header = self.parse_header_buffer(&header_buffer)?;
282
283 if &header.signature != b"MSWIM\x00\x00\x00" {
285 return Err(anyhow::anyhow!("无效的 WIM 文件签名"));
286 }
287
288 info!(
289 "成功读取 WIM 文件头 - 版本: {}, 镜像数: {}",
290 header.format_version, header.image_count
291 );
292
293 self.header = Some(header);
294 }
295 Ok(self.header.as_ref().unwrap())
296 }
297
298 fn parse_header_buffer(&self, buffer: &[u8]) -> Result<WimHeader> {
300 use std::convert::TryInto;
301
302 let read_u32_le = |offset: usize| -> u32 {
304 u32::from_le_bytes(buffer[offset..offset + 4].try_into().unwrap())
305 };
306
307 let read_u16_le = |offset: usize| -> u16 {
308 u16::from_le_bytes(buffer[offset..offset + 2].try_into().unwrap())
309 };
310
311 let read_u64_le = |offset: usize| -> u64 {
312 u64::from_le_bytes(buffer[offset..offset + 8].try_into().unwrap())
313 };
314
315 let parse_resource_entry = |offset: usize| -> FileResourceEntry {
317 let size_bytes = &buffer[offset..offset + 7];
319 let mut size_array = [0u8; 8];
320 size_array[..7].copy_from_slice(size_bytes);
321 let size = u64::from_le_bytes(size_array);
322
323 let flags = buffer[offset + 7];
324 let offset_val = read_u64_le(offset + 8);
325 let original_size = read_u64_le(offset + 16);
326
327 FileResourceEntry {
328 size,
329 flags,
330 offset: offset_val,
331 original_size,
332 }
333 };
334
335 let mut signature = [0u8; 8];
337 signature.copy_from_slice(&buffer[0..8]);
338
339 let header = WimHeader {
340 signature,
341 header_size: read_u32_le(8),
342 format_version: read_u32_le(12),
343 file_flags: read_u32_le(16),
344 compressed_size: read_u32_le(20),
345 guid: buffer[24..40].try_into().unwrap(),
346 segment_number: read_u16_le(40),
347 total_segments: read_u16_le(42),
348 image_count: read_u32_le(44),
349 offset_table_resource: parse_resource_entry(48),
350 xml_data_resource: parse_resource_entry(72),
351 boot_metadata_resource: parse_resource_entry(96),
352 bootable_image_index: read_u32_le(120),
353 integrity_resource: parse_resource_entry(124),
354 };
355
356 debug!(
357 "解析 WIM 头部完成 - 镜像数: {}, 文件标志: 0x{:08X}",
358 header.image_count, header.file_flags
359 );
360
361 Ok(header)
362 }
363
364 pub fn read_xml_data(&mut self) -> Result<()> {
366 if self.header.is_none() {
368 self.read_header()?;
369 }
370
371 let header = self.header.as_ref().unwrap();
372
373 if header.xml_data_resource.size == 0 {
375 return Err(anyhow::anyhow!("WIM 文件中没有 XML 数据资源"));
376 }
377
378 debug!(
379 "开始读取 XML 数据,偏移: {}, 大小: {}",
380 header.xml_data_resource.offset, header.xml_data_resource.size
381 );
382
383 self.file
385 .seek(SeekFrom::Start(header.xml_data_resource.offset))?;
386
387 let mut xml_buffer = vec![0u8; header.xml_data_resource.size as usize];
389 self.file
390 .read_exact(&mut xml_buffer)
391 .context("读取 XML 数据失败")?;
392
393 self.parse_xml_data(&xml_buffer)?;
395
396 info!("成功解析 {} 个镜像的信息", self.images.len());
397 Ok(())
398 }
399
400 fn parse_xml_data(&mut self, xml_buffer: &[u8]) -> Result<()> {
402 if xml_buffer.len() < 2 {
404 return Err(anyhow::anyhow!("XML 数据太短"));
405 }
406
407 if xml_buffer[0] != 0xFF || xml_buffer[1] != 0xFE {
409 return Err(anyhow::anyhow!("无效的 XML 数据 BOM"));
410 }
411
412 let xml_utf16_data = &xml_buffer[2..]; if !xml_utf16_data.len().is_multiple_of(2) {
417 return Err(anyhow::anyhow!("XML UTF-16 数据长度不是偶数"));
418 }
419
420 let mut utf16_chars = Vec::new();
422 for chunk in xml_utf16_data.chunks_exact(2) {
423 let char_val = u16::from_le_bytes([chunk[0], chunk[1]]);
424 utf16_chars.push(char_val);
425 }
426
427 let xml_string = String::from_utf16(&utf16_chars).context("无法将 XML 数据转换为 UTF-8")?;
429
430 debug!("XML 数据长度: {} 字符", xml_string.len());
431
432 self.parse_xml_images(&xml_string)?;
434
435 Ok(())
436 }
437
438 fn parse_xml_data_optimized(&mut self, xml_buffer: &[u8]) -> Result<()> {
440 if xml_buffer.len() < 2 {
442 return Err(anyhow::anyhow!("XML 数据太短"));
443 }
444
445 if xml_buffer[0] != 0xFF || xml_buffer[1] != 0xFE {
447 return Err(anyhow::anyhow!("无效的 XML 数据 BOM"));
448 }
449
450 let (xml_string, _, had_errors) = UTF_16LE.decode(&xml_buffer[2..]);
452 if had_errors {
453 return Err(anyhow::anyhow!("UTF-16解码过程中发现错误"));
454 }
455
456 debug!("XML 数据长度: {} 字符", xml_string.len());
457
458 self.parse_xml_images_optimized(&xml_string)?;
460
461 Ok(())
462 }
463
464 fn parse_xml_images_optimized(&mut self, xml_content: &str) -> Result<()> {
466 self.images.clear();
467
468 let mut reader = Reader::from_str(xml_content);
469 reader.config_mut().trim_text(true);
470
471 let mut current_image: Option<ImageInfo> = None;
472 let mut current_tag = String::new();
473 let mut in_windows_section = false;
474
475 loop {
476 match reader.read_event() {
477 Ok(Event::Start(ref e)) => {
478 match e.name().as_ref() {
479 b"IMAGE" => {
480 for attr in e.attributes().flatten() {
482 if attr.key.as_ref() == b"INDEX" {
483 if let Ok(index_str) = std::str::from_utf8(&attr.value) {
484 if let Ok(index) = index_str.parse::<u32>() {
485 current_image = Some(ImageInfo::new_with_index(index));
486 }
487 }
488 }
489 }
490 }
491 b"WINDOWS" => {
492 in_windows_section = true;
493 }
494 tag => {
495 current_tag = String::from_utf8_lossy(tag).into_owned();
496 }
497 }
498 }
499 Ok(Event::Text(e)) => {
500 if let Some(ref mut image) = current_image {
501 let text = std::str::from_utf8(&e)?;
503
504 if in_windows_section && current_tag == "ARCH" {
506 image.set_field("ARCH", text);
507 } else if !in_windows_section {
508 image.set_field(¤t_tag, text);
510 }
511 }
512 }
513 Ok(Event::End(ref e)) => {
514 match e.name().as_ref() {
515 b"IMAGE" => {
516 if let Some(mut image) = current_image.take() {
517 image.infer_version_and_arch();
519 self.images.push(image);
520 }
521 }
522 b"WINDOWS" => {
523 in_windows_section = false;
524 }
525 _ => {}
526 }
527 }
528 Ok(Event::Eof) => break,
529 Err(e) => return Err(anyhow::anyhow!("XML解析错误: {}", e)),
530 _ => {}
531 }
532 }
533
534 info!("优化解析完成:成功解析 {} 个镜像的信息", self.images.len());
535 Ok(())
536 }
537
538 fn parse_xml_images(&mut self, xml_content: &str) -> Result<()> {
540 self.images.clear();
544
545 let mut start_pos = 0;
547 while let Some(image_start) = xml_content[start_pos..].find("<IMAGE") {
548 let absolute_start = start_pos + image_start;
549
550 if let Some(image_end) = xml_content[absolute_start..].find("</IMAGE>") {
552 let absolute_end = absolute_start + image_end + 8; let image_xml = &xml_content[absolute_start..absolute_end];
554
555 if let Ok(image_info) = self.parse_single_image_xml(image_xml) {
557 self.images.push(image_info);
558 }
559
560 start_pos = absolute_end;
561 } else {
562 break;
563 }
564 }
565
566 Ok(())
567 }
568
569 pub fn parse_single_image_xml(&self, image_xml: &str) -> Result<ImageInfo> {
571 let extract_tag_value = |xml: &str, tag: &str| -> Option<String> {
573 let start_tag = format!("<{tag}>");
574 let end_tag = format!("</{tag}>");
575
576 if let Some(start) = xml.find(&start_tag) {
577 if let Some(end) = xml.find(&end_tag) {
578 let value_start = start + start_tag.len();
579 if value_start < end {
580 return Some(xml[value_start..end].trim().to_string());
581 }
582 }
583 }
584 None
585 };
586
587 let index = if let Some(index_start) = image_xml.find("INDEX=\"") {
589 let index_value_start = index_start + 7; if let Some(index_end) = image_xml[index_value_start..].find("\"") {
591 let index_str = &image_xml[index_value_start..index_value_start + index_end];
592 index_str.parse().unwrap_or(0)
593 } else {
594 0
595 }
596 } else {
597 0
598 };
599
600 let name =
602 extract_tag_value(image_xml, "DISPLAYNAME").unwrap_or_else(|| format!("Image {index}"));
603 let description = extract_tag_value(image_xml, "DISPLAYDESCRIPTION")
604 .unwrap_or_else(|| "Unknown".to_string());
605 let dir_count = extract_tag_value(image_xml, "DIRCOUNT")
606 .and_then(|s| s.parse().ok())
607 .unwrap_or(0);
608 let file_count = extract_tag_value(image_xml, "FILECOUNT")
609 .and_then(|s| s.parse().ok())
610 .unwrap_or(0);
611 let total_bytes = extract_tag_value(image_xml, "TOTALBYTES")
612 .and_then(|s| s.parse().ok())
613 .unwrap_or(0);
614
615 let arch_from_xml = self.parse_arch_from_xml(image_xml);
617
618 let (version, arch_from_name) = self.extract_version_and_arch(&name, &description);
620 let architecture = arch_from_xml.or(arch_from_name);
621
622 let image_info = ImageInfo {
623 index,
624 name,
625 description,
626 dir_count,
627 file_count,
628 total_bytes,
629 creation_time: None, last_modification_time: None, version,
632 architecture,
633 };
634
635 debug!(
636 "解析镜像信息: {} - {} - {} - {:#?}",
637 image_info.index, image_info.name, image_info.description, image_info.architecture
638 );
639
640 Ok(image_info)
641 }
642
643 fn extract_version_and_arch(
645 &self,
646 name: &str,
647 description: &str,
648 ) -> (Option<String>, Option<String>) {
649 let combined_text = format!("{name} {description}").to_lowercase();
650
651 let version = if combined_text.contains("windows 11") {
653 Some("Windows 11".to_string())
654 } else if combined_text.contains("windows 10") {
655 Some("Windows 10".to_string())
656 } else if combined_text.contains("windows server 2022") {
657 Some("Windows Server 2022".to_string())
658 } else if combined_text.contains("windows server 2019") {
659 Some("Windows Server 2019".to_string())
660 } else if combined_text.contains("windows server") {
661 Some("Windows Server".to_string())
662 } else if combined_text.contains("windows") {
663 Some("Windows".to_string())
664 } else {
665 None
666 };
667
668 let architecture = if combined_text.contains("x64") || combined_text.contains("amd64") {
670 Some("x64".to_string())
671 } else if combined_text.contains("x86") {
672 Some("x86".to_string())
673 } else if combined_text.contains("arm64") {
674 Some("ARM64".to_string())
675 } else {
676 None
677 };
678
679 (version, architecture)
680 }
681
682 pub fn parse_arch_from_xml(&self, image_xml: &str) -> Option<String> {
684 let extract_tag_value = |xml: &str, tag: &str| -> Option<String> {
686 let start_tag = format!("<{tag}>");
687 let end_tag = format!("</{tag}>");
688
689 if let Some(start) = xml.find(&start_tag) {
690 if let Some(end) = xml.find(&end_tag) {
691 let value_start = start + start_tag.len();
692 if value_start < end {
693 return Some(xml[value_start..end].trim().to_string());
694 }
695 }
696 }
697 None
698 };
699
700 if let Some(arch_value) = extract_tag_value(image_xml, "ARCH") {
702 match arch_value.as_str() {
703 "0" => Some("x86".to_string()),
704 "9" => Some("x64".to_string()),
705 "5" => Some("ARM".to_string()),
706 "12" => Some("ARM64".to_string()),
707 _ => {
708 debug!("未知的架构值: {}", arch_value);
709 None
710 }
711 }
712 } else {
713 None
714 }
715 }
716
717 pub fn get_images(&self) -> &[ImageInfo] {
719 &self.images
720 }
721
722 #[allow(dead_code)]
724 pub fn get_image(&self, index: u32) -> Option<&ImageInfo> {
725 self.images.iter().find(|img| img.index == index)
726 }
727
728 #[allow(dead_code)]
730 pub fn get_header(&self) -> Option<&WimHeader> {
731 self.header.as_ref()
732 }
733
734 #[allow(dead_code)]
736 pub fn has_multiple_images(&self) -> bool {
737 self.header
738 .as_ref()
739 .map(|h| h.image_count > 1)
740 .unwrap_or(false)
741 }
742
743 #[allow(dead_code)]
745 pub fn get_image_count(&self) -> u32 {
746 self.header.as_ref().map(|h| h.image_count).unwrap_or(0)
747 }
748
749 #[allow(dead_code)]
751 pub fn is_compressed(&self) -> bool {
752 self.header
753 .as_ref()
754 .map(|h| h.file_flags & FileFlags::COMPRESSION != 0)
755 .unwrap_or(false)
756 }
757
758 #[allow(dead_code)]
760 pub fn get_compression_type(&self) -> Option<&'static str> {
761 if let Some(header) = &self.header {
762 if header.file_flags & FileFlags::COMPRESS_XPRESS != 0 {
763 Some("XPRESS")
764 } else if header.file_flags & FileFlags::COMPRESS_LZX != 0 {
765 Some("LZX")
766 } else if header.file_flags & FileFlags::COMPRESSION != 0 {
767 Some("Unknown")
768 } else {
769 None
770 }
771 } else {
772 None
773 }
774 }
775
776 pub fn parse_full(&mut self) -> Result<()> {
778 self.read_header()?;
779 self.read_xml_data()?;
780 Ok(())
781 }
782}
783
784impl std::fmt::Display for ImageInfo {
785 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
786 write!(f, "镜像 {} - {}", self.index, self.name)?;
787 if let Some(ref version) = self.version {
788 write!(f, " [{version}]")?;
789 }
790 if let Some(ref arch) = self.architecture {
791 write!(f, " [{arch}]")?;
792 }
793 write!(f, " | 描述: {}", self.description)?;
794 write!(
795 f,
796 " | 文件数: {}, 目录数: {}",
797 self.file_count, self.dir_count
798 )?;
799 write!(f, " | 总大小: {} MB", self.total_bytes / (1024 * 1024))?;
800 Ok(())
801 }
802}
803
804impl std::fmt::Display for WimHeader {
805 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806 writeln!(f, "WIM Header:")?;
807 writeln!(f, " Format Version: {}", self.format_version)?;
808 writeln!(f, " File Flags: 0x{:08X}", self.file_flags)?;
809 writeln!(f, " Image Count: {}", self.image_count)?;
810 writeln!(
811 f,
812 " Segment: {}/{}",
813 self.segment_number, self.total_segments
814 )?;
815 writeln!(f, " Bootable Image Index: {}", self.bootable_image_index)?;
816 Ok(())
817 }
818}
819
820#[allow(dead_code)]
821impl WimParser {
822 #[allow(dead_code)]
824 pub fn get_version_summary(&self) -> Vec<String> {
825 let mut summaries = Vec::new();
826
827 for image in &self.images {
828 let mut summary = format!("镜像 {}: {}", image.index, image.name);
829
830 if let Some(ref version) = image.version {
831 summary.push_str(&format!(" ({version})"));
832 }
833
834 if let Some(ref arch) = image.architecture {
835 summary.push_str(&format!(" [{arch}]"));
836 }
837
838 summaries.push(summary);
839 }
840
841 summaries
842 }
843
844 pub fn get_primary_version(&self) -> Option<String> {
846 if self.images.is_empty() {
847 return None;
848 }
849
850 let mut version_counts = std::collections::HashMap::new();
852 for image in &self.images {
853 if let Some(ref version) = image.version {
854 *version_counts.entry(version.clone()).or_insert(0) += 1;
855 }
856 }
857
858 version_counts
860 .into_iter()
861 .max_by_key(|(_, count)| *count)
862 .map(|(version, _)| version)
863 }
864
865 pub fn get_primary_architecture(&self) -> Option<String> {
867 if self.images.is_empty() {
868 return None;
869 }
870
871 let mut arch_counts = std::collections::HashMap::new();
873 for image in &self.images {
874 if let Some(ref arch) = image.architecture {
875 *arch_counts.entry(arch.clone()).or_insert(0) += 1;
876 }
877 }
878
879 arch_counts
881 .into_iter()
882 .max_by_key(|(_, count)| *count)
883 .map(|(arch, _)| arch)
884 }
885
886 #[allow(dead_code)]
888 pub fn has_version(&self, version: &str) -> bool {
889 self.images.iter().any(|img| {
890 img.version
891 .as_ref()
892 .is_some_and(|v| v.to_lowercase().contains(&version.to_lowercase()))
893 })
894 }
895
896 #[allow(dead_code)]
898 pub fn has_architecture(&self, arch: &str) -> bool {
899 self.images.iter().any(|img| {
900 img.architecture
901 .as_ref()
902 .is_some_and(|a| a.to_lowercase().contains(&arch.to_lowercase()))
903 })
904 }
905
906 pub fn get_windows_info(&self) -> Option<WindowsInfo> {
908 let primary_version = self.get_primary_version()?;
909 let primary_arch = self.get_primary_architecture()?;
910
911 if !primary_version.to_lowercase().contains("windows") {
913 return None;
914 }
915
916 let mut editions = Vec::new();
918 for image in &self.images {
919 let name_lower = image.name.to_lowercase();
920 if name_lower.contains("pro") && !editions.contains(&"Pro".to_string()) {
921 editions.push("Pro".to_string());
922 } else if name_lower.contains("home") && !editions.contains(&"Home".to_string()) {
923 editions.push("Home".to_string());
924 } else if name_lower.contains("enterprise")
925 && !editions.contains(&"Enterprise".to_string())
926 {
927 editions.push("Enterprise".to_string());
928 } else if name_lower.contains("education")
929 && !editions.contains(&"Education".to_string())
930 {
931 editions.push("Education".to_string());
932 }
933 }
934
935 Some(WindowsInfo {
936 version: primary_version,
937 architecture: primary_arch,
938 editions,
939 image_count: self.images.len() as u32,
940 total_size: self.images.iter().map(|img| img.total_bytes).sum(),
941 })
942 }
943}
944
945#[derive(Debug, Clone)]
947pub struct WindowsInfo {
948 pub version: String,
949 pub architecture: String,
950 pub editions: Vec<String>,
951 pub image_count: u32,
952 pub total_size: u64,
953}
954
955impl std::fmt::Display for WindowsInfo {
956 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
957 write!(f, "{} ({})", self.version, self.architecture)?;
958 if !self.editions.is_empty() {
959 write!(f, " - 版本: {}", self.editions.join(", "))?;
960 }
961 write!(f, " | 镜像数量: {}", self.image_count)?;
962 write!(f, " | 总大小: {} MB", self.total_size / (1024 * 1024))?;
963 Ok(())
964 }
965}
966
967#[cfg(any(test, feature = "benchmarking"))]
969impl WimParser {
970 pub fn parse_xml_data_for_bench(&mut self, xml_buffer: &[u8]) -> Result<()> {
972 self.parse_xml_data(xml_buffer)
973 }
974
975 pub fn parse_xml_data_optimized_for_bench(&mut self, xml_buffer: &[u8]) -> Result<()> {
977 self.parse_xml_data_optimized(xml_buffer)
978 }
979
980 pub fn use_optimized_parsing(&mut self, xml_buffer: &[u8]) -> Result<()> {
982 self.parse_xml_data_optimized(xml_buffer)
983 }
984}