diff --git a/Cargo.lock b/Cargo.lock
index e156095..ab28626 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2661,9 +2661,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.19.1"
+version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
+checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.2",
@@ -3126,6 +3126,28 @@ dependencies = [
"wit-bindgen-rt",
]
+[[package]]
+name = "win-installer"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "color-eyre",
+ "core",
+ "crossterm",
+ "futures",
+ "ratatui",
+ "serde",
+ "tempfile",
+ "tokio",
+ "toml",
+ "tracing",
+ "tracing-error",
+ "tracing-subscriber",
+ "vergen-gix",
+ "xml",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -3354,6 +3376,12 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+[[package]]
+name = "xml"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "838dd679b10a4180431ce7c2caa6e5585a7c8f63154c19ae99345126572e80cc"
+
[[package]]
name = "yaml-rust2"
version = "0.10.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7dc2384..0a6700c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,6 @@
# along with Deja-Vu. If not, see .
[workspace]
-members = ["core", "boot_diags", "deja_vu", "pe_menu"]
-default-members = ["core", "boot_diags", "deja_vu", "pe_menu"]
+members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
+default-members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
resolver = "2"
diff --git a/win_installer/Cargo.toml b/win_installer/Cargo.toml
new file mode 100644
index 0000000..d7bcc76
--- /dev/null
+++ b/win_installer/Cargo.toml
@@ -0,0 +1,48 @@
+# This file is part of Deja-Vu.
+#
+# Deja-Vu is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Deja-Vu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Deja-Vu. If not, see .
+
+[package]
+name = "win-installer"
+authors = ["2Shirt <2xShirt@gmail.com>"]
+edition = "2024"
+license = "GPL"
+version = "0.1.0"
+
+[dependencies]
+core = { path = "../core" }
+clap = { version = "4.4.5", features = [
+ "derive",
+ "cargo",
+ "wrap_help",
+ "unicode",
+ "string",
+ "unstable-styles",
+ ] }
+color-eyre = "0.6.3"
+crossterm = { version = "0.28.1", features = ["event-stream"] }
+futures = "0.3.30"
+ratatui = "0.29.0"
+serde = { version = "1.0.217", features = ["derive"] }
+tokio = { version = "1.43.0", features = ["full"] }
+toml = "0.8.13"
+tracing = "0.1.41"
+tracing-error = "0.2.0"
+tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
+tempfile = "3.23.0"
+xml = "1.1.0"
+
+[build-dependencies]
+anyhow = "1.0.86"
+vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
diff --git a/win_installer/build.rs b/win_installer/build.rs
new file mode 100644
index 0000000..8988d39
--- /dev/null
+++ b/win_installer/build.rs
@@ -0,0 +1,28 @@
+// This file is part of Deja-Vu.
+//
+// Deja-Vu is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Deja-Vu is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Deja-Vu. If not, see .
+//
+use anyhow::Result;
+use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder};
+
+fn main() -> Result<()> {
+ let build = BuildBuilder::all_build()?;
+ let gix = GixBuilder::all_git()?;
+ let cargo = CargoBuilder::all_cargo()?;
+ Emitter::default()
+ .add_instructions(&build)?
+ .add_instructions(&gix)?
+ .add_instructions(&cargo)?
+ .emit()
+}
diff --git a/win_installer/src/app.rs b/win_installer/src/app.rs
new file mode 100644
index 0000000..da38f04
--- /dev/null
+++ b/win_installer/src/app.rs
@@ -0,0 +1,315 @@
+// This file is part of Deja-Vu.
+//
+// Deja-Vu is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Deja-Vu is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Deja-Vu. If not, see .
+//
+use core::{
+ action::Action,
+ components::{Component, footer::Footer, left::Left, popup, right::Right, title::Title},
+ config::Config,
+ state::Mode,
+ tasks::{TaskType, Tasks},
+ tui::{Event, Tui},
+};
+use std::{
+ iter::zip,
+ path::PathBuf,
+ sync::{Arc, Mutex},
+};
+
+use color_eyre::Result;
+use ratatui::{
+ crossterm::event::KeyEvent,
+ layout::{Constraint, Direction, Layout},
+ prelude::Rect,
+};
+use tokio::sync::mpsc;
+use tracing::{debug, info};
+
+use crate::state::State;
+
+pub struct App<'a> {
+ // TUI
+ action_rx: mpsc::UnboundedReceiver,
+ action_tx: mpsc::UnboundedSender,
+ components: Vec>,
+ config: Config,
+ frame_rate: f64,
+ last_tick_key_events: Vec,
+ should_quit: bool,
+ should_suspend: bool,
+ tick_rate: f64,
+ // App
+ state: State<'a>,
+ mode: Mode,
+ tasks: Tasks,
+}
+
+impl App<'_> {
+ pub fn new(tick_rate: f64, frame_rate: f64) -> Result {
+ let (action_tx, action_rx) = mpsc::unbounded_channel();
+ let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
+ let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
+ Ok(Self {
+ // TUI
+ action_rx,
+ action_tx,
+ components: vec![
+ Box::new(Title::new("Windows Install Tool")),
+ Box::new(Left::new()),
+ Box::new(Right::new()),
+ Box::new(Footer::new()),
+ Box::new(popup::Popup::new()),
+ ],
+ config: Config::new()?,
+ frame_rate,
+ last_tick_key_events: Vec::new(),
+ should_quit: false,
+ should_suspend: false,
+ tick_rate,
+ // App
+ mode: Mode::default(),
+ state: State::new(disk_list_arc),
+ tasks,
+ })
+ }
+
+ pub async fn run(&mut self) -> Result<()> {
+ let mut tui = Tui::new()?
+ // .mouse(true) // uncomment this line to enable mouse support
+ .tick_rate(self.tick_rate)
+ .frame_rate(self.frame_rate);
+ tui.enter()?;
+
+ for component in &mut self.components {
+ component.register_action_handler(self.action_tx.clone())?;
+ }
+ for component in &mut self.components {
+ component.register_config_handler(self.config.clone())?;
+ }
+ for component in &mut self.components {
+ component.init(tui.size()?)?;
+ }
+
+ let action_tx = self.action_tx.clone();
+ action_tx.send(Action::SetMode(Mode::PEMenu))?;
+ loop {
+ self.handle_events(&mut tui).await?;
+ self.handle_actions(&mut tui)?;
+ if self.should_suspend {
+ tui.suspend()?;
+ action_tx.send(Action::Resume)?;
+ action_tx.send(Action::ClearScreen)?;
+ // tui.mouse(true);
+ tui.enter()?;
+ } else if self.should_quit {
+ tui.stop()?;
+ break;
+ }
+ }
+ tui.exit()?;
+ Ok(())
+ }
+
+ async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
+ let Some(event) = tui.next_event().await else {
+ return Ok(());
+ };
+ let action_tx = self.action_tx.clone();
+ match event {
+ Event::Quit => action_tx.send(Action::Quit)?,
+ Event::Tick => action_tx.send(Action::Tick)?,
+ Event::Render => action_tx.send(Action::Render)?,
+ Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
+ Event::Key(key) => self.handle_key_event(key)?,
+ _ => {}
+ }
+ for component in &mut self.components {
+ if let Some(action) = component.handle_events(Some(event.clone()))? {
+ action_tx.send(action)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
+ let action_tx = self.action_tx.clone();
+ let Some(keymap) = self.config.keybindings.get(&self.mode) else {
+ return Ok(());
+ };
+ if let Some(action) = keymap.get(&vec![key]) {
+ info!("Got action: {action:?}");
+ action_tx.send(action.clone())?;
+ } else {
+ // If the key was not handled as a single key action,
+ // then consider it for multi-key combinations.
+ self.last_tick_key_events.push(key);
+
+ // Check for multi-key combinations
+ if let Some(action) = keymap.get(&self.last_tick_key_events) {
+ info!("Got action: {action:?}");
+ action_tx.send(action.clone())?;
+ }
+ }
+ Ok(())
+ }
+
+ fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> {
+ while let Ok(action) = self.action_rx.try_recv() {
+ if action != Action::Tick && action != Action::Render {
+ debug!("{action:?}");
+ }
+ match action {
+ Action::Tick => {
+ self.last_tick_key_events.drain(..);
+ // Check background task(s)
+ self.tasks.poll()?;
+ }
+ Action::Quit => self.should_quit = true,
+ Action::Suspend => self.should_suspend = true,
+ Action::Resume => self.should_suspend = false,
+ Action::ClearScreen => tui.terminal.clear()?,
+ // Action::KeyUp => {
+ // self.list.previous();
+ // if let Some(tool) = self.list.get_selected()
+ // && tool.separator
+ // {
+ // // Skip over separator
+ // self.list.previous();
+ // if let Some(index) = self.list.selected() {
+ // self.action_tx.send(Action::Highlight(index))?;
+ // }
+ // }
+ // }
+ // Action::KeyDown => {
+ // self.list.next();
+ // if let Some(tool) = self.list.get_selected()
+ // && tool.separator
+ // {
+ // // Skip over separator
+ // self.list.next();
+ // if let Some(index) = self.list.selected() {
+ // self.action_tx.send(Action::Highlight(index))?;
+ // }
+ // }
+ // }
+ Action::Error(ref msg) => {
+ self.action_tx
+ .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?;
+ self.action_tx.send(Action::SetMode(Mode::Failed))?;
+ }
+ // Action::Process => {
+ // // Run selected tool
+ // if let Some(tool) = self.list.get_selected() {
+ // info!("Run tool: {:?}", &tool);
+ // self.tasks.add(build_tool_command(self, &tool));
+ // }
+ // }
+ Action::Resize(w, h) => self.handle_resize(tui, w, h)?,
+ Action::Render => self.render(tui)?,
+ Action::SetMode(mode) => {
+ self.mode = mode;
+ self.action_tx.send(Action::UpdateFooter(String::from(
+ "(Enter) to select / (t) for terminal / (p) to power off / (r) to restart",
+ )))?;
+ //self.action_tx.send(build_left_items(self))?;
+ //self.action_tx.send(build_right_items(self))?;
+ self.action_tx.send(Action::Select(None, None))?;
+ }
+ _ => {}
+ }
+ for component in &mut self.components {
+ if let Some(action) = component.update(action.clone())? {
+ self.action_tx.send(action)?;
+ };
+ }
+ }
+ Ok(())
+ }
+
+ fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> {
+ tui.resize(Rect::new(0, 0, w, h))?;
+ self.render(tui)?;
+ Ok(())
+ }
+
+ fn render(&mut self, tui: &mut Tui) -> Result<()> {
+ tui.draw(|frame| {
+ if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] {
+ let component_areas = vec![header, left, right, footer, popup];
+ for (component, area) in zip(self.components.iter_mut(), component_areas) {
+ if let Err(err) = component.draw(frame, area) {
+ let _ = self
+ .action_tx
+ .send(Action::Error(format!("Failed to draw: {err:?}")));
+ }
+ }
+ };
+ })?;
+ Ok(())
+ }
+}
+
+fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
+ // Cut the given rectangle into three vertical pieces
+ let popup_layout = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([
+ Constraint::Percentage((100 - percent_y) / 2),
+ Constraint::Percentage(percent_y),
+ Constraint::Percentage((100 - percent_y) / 2),
+ ])
+ .split(r);
+
+ // Then cut the middle vertical piece into three width-wise pieces
+ Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([
+ Constraint::Percentage((100 - percent_x) / 2),
+ Constraint::Percentage(percent_x),
+ Constraint::Percentage((100 - percent_x) / 2),
+ ])
+ .split(popup_layout[1])[1] // Return the middle chunk
+}
+
+fn get_chunks(r: Rect) -> Vec {
+ let mut chunks: Vec = Vec::with_capacity(6);
+
+ // Main sections
+ chunks.extend(
+ Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([
+ Constraint::Length(3),
+ Constraint::Min(1),
+ Constraint::Length(3),
+ ])
+ .split(r)
+ .to_vec(),
+ );
+
+ // Left/Right
+ chunks.extend(
+ Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
+ .split(centered_rect(90, 90, chunks[1]))
+ .to_vec(),
+ );
+
+ // Popup
+ chunks.push(centered_rect(60, 25, r));
+
+ // Done
+ chunks
+}
diff --git a/win_installer/src/main.rs b/win_installer/src/main.rs
new file mode 100644
index 0000000..88a0f38
--- /dev/null
+++ b/win_installer/src/main.rs
@@ -0,0 +1,34 @@
+// This file is part of Deja-Vu.
+//
+// Deja-Vu is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Deja-Vu is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Deja-Vu. If not, see .
+//
+use clap::Parser;
+use color_eyre::Result;
+
+use crate::app::App;
+
+mod app;
+mod state;
+mod wim;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ core::errors::init()?;
+ core::logging::init()?;
+
+ let args = core::cli::Cli::parse();
+ let mut app = App::new(args.tick_rate, args.frame_rate)?;
+ app.run().await?;
+ Ok(())
+}
diff --git a/win_installer/src/state.rs b/win_installer/src/state.rs
new file mode 100644
index 0000000..48a0086
--- /dev/null
+++ b/win_installer/src/state.rs
@@ -0,0 +1,46 @@
+// This file is part of Deja-Vu.
+//
+// Deja-Vu is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Deja-Vu is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Deja-Vu. If not, see .
+//
+
+use std::sync::{Arc, Mutex};
+
+use core::system::{disk::Disk, drivers};
+
+use crate::wim::WimSources;
+
+#[derive(Debug, Default)]
+pub struct State<'a> {
+ pub disk_index_dest: Option,
+ pub disk_list: Arc>>,
+ pub driver: Option,
+ pub driver_list: Vec,
+ pub part_index_boot: Option,
+ pub wim_file_index: Option,
+ pub wim_image_index: Option,
+ pub wim_sources: Arc>>,
+}
+
+impl State<'_> {
+ pub fn new(disk_list: Arc>>) -> Self {
+ State {
+ disk_list,
+ ..Default::default()
+ }
+ }
+
+ pub fn scan_drivers(&mut self) {
+ self.driver_list = drivers::scan();
+ }
+}
diff --git a/win_installer/src/wim.rs b/win_installer/src/wim.rs
new file mode 100644
index 0000000..96cea57
--- /dev/null
+++ b/win_installer/src/wim.rs
@@ -0,0 +1,218 @@
+// This file is part of Deja-Vu.
+//
+// Deja-Vu is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Deja-Vu is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Deja-Vu. If not, see .
+//
+use std::{
+ collections::HashMap, fmt, fs::File, io::BufReader, path::Path, process::Command,
+ sync::LazyLock,
+};
+
+use tempfile::NamedTempFile;
+use xml::reader::{EventReader, XmlEvent};
+
+static WIN_BUILDS: LazyLock> = LazyLock::new(|| {
+ HashMap::from([
+ // Windows 10
+ ("10240", "1507 \"Threshold 1\""),
+ ("10586", "1511 \"Threshold 2\""),
+ ("14393", "1607 \"Redstone 1\""),
+ ("15063", "1703 \"Redstone 2\""),
+ ("16299", "1709 \"Redstone 3\""),
+ ("17134", "1803 \"Redstone 4\""),
+ ("17763", "1809 \"Redstone 5\""),
+ ("18362", "1903 / 19H1"),
+ ("18363", "1909 / 19H2"),
+ ("19041", "2004 / 20H1"),
+ ("19042", "20H2"),
+ ("19043", "21H1"),
+ ("19044", "21H2"),
+ ("19045", "22H2"),
+ // Windows 11
+ ("22000", "21H2"),
+ ("22621", "22H2"),
+ ("22631", "23H2"),
+ ("26100", "24H2"),
+ ("26200", "25H2"),
+ ])
+});
+
+#[derive(Debug)]
+struct WimFile<'a> {
+ path: &'a Path,
+ images: Vec,
+}
+
+#[derive(Clone, Debug, Default)]
+struct WimImage {
+ build: String,
+ index: String,
+ name: String,
+ spbuild: String,
+ version: String,
+}
+
+impl WimImage {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn reset(&mut self) {
+ self.build.clear();
+ self.index.clear();
+ self.name.clear();
+ self.spbuild.clear();
+ self.version.clear();
+ }
+}
+
+impl fmt::Display for WimImage {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Windows 11 Home (24H2, 26100.xxxx)
+ let s = if self.version.is_empty() {
+ String::new()
+ } else {
+ format!("{}, ", self.version)
+ };
+ write!(f, "{} ({}{}.{})", self.name, s, self.build, self.spbuild)
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct WimSources<'a> {
+ files: Vec>,
+}
+
+impl WimSources<'_> {
+ pub fn new() -> Self {
+ Default::default()
+ }
+}
+
+fn get_wim_xml(wim_file: &str) -> std::io::Result {
+ let tmp_file = NamedTempFile::new()?;
+ let _ = Command::new("wiminfo")
+ .args([
+ wim_file,
+ "--extract-xml",
+ tmp_file.path().as_os_str().to_str().unwrap(),
+ ])
+ .output()
+ .expect("Failed to extract XML data");
+ let file = File::open(tmp_file.path())?;
+
+ Ok(file)
+}
+
+fn parse_wim_file(wim_file: &str) -> std::io::Result> {
+ let mut wim_images: Vec = Vec::new();
+ let wim_path = Path::new(wim_file);
+ if !wim_path.exists() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "Failed to read WIM file",
+ ));
+ };
+ let xml_file = get_wim_xml(wim_file).expect("Failed to open XML file");
+ let file = BufReader::new(xml_file);
+
+ let mut current_element = String::new();
+ let mut image = WimImage::new();
+
+ let parser = EventReader::new(file);
+ let mut depth = 0;
+ for e in parser {
+ match e {
+ Ok(XmlEvent::StartElement {
+ name, attributes, ..
+ }) => {
+ println!("{:spaces$}+{name}", "", spaces = depth * 2);
+ depth += 1;
+ current_element = name.local_name.to_uppercase();
+
+ if current_element == "IMAGE" {
+ // Update index
+ if let Some(attr) = attributes.first()
+ && attr.name.to_string().to_lowercase() == "index"
+ {
+ image.index = attr.value.clone();
+ }
+ }
+ }
+ Ok(XmlEvent::Characters(char_data)) => {
+ if current_element == "BUILD" {
+ let build = char_data.trim();
+ image.build = build.to_string();
+ image.version = WIN_BUILDS.get(build).map_or("", |v| v).to_string();
+ println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
+ }
+ if current_element == "NAME" {
+ image.name = char_data.trim().to_string();
+ println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
+ }
+ if current_element == "SPBUILD" {
+ image.spbuild = char_data.trim().to_string();
+ println!("{:spaces$} {char_data}", "", spaces = (depth + 1) * 2);
+ }
+ }
+ Ok(XmlEvent::EndElement { name }) => {
+ depth -= 1;
+ println!("{:spaces$}-{name}", "", spaces = depth * 2);
+
+ if name.local_name.to_uppercase() == "IMAGE" {
+ // Append image to list
+ if !image.build.is_empty() && !image.name.is_empty() && !image.index.is_empty()
+ {
+ wim_images.push(image.clone());
+ }
+
+ // Reset image
+ image.reset()
+ }
+ }
+ Err(e) => {
+ eprintln!("Error: {e}");
+ break;
+ }
+ _ => {}
+ }
+ }
+ let wim_file = WimFile {
+ path: wim_path,
+ images: wim_images,
+ };
+
+ Ok(wim_file)
+}
+
+// fn main() -> std::io::Result<()> {
+// let mut sources = WimSources::new();
+// if let Ok(wim_file) = parse_wim_file("./23H2.wim")
+// && !wim_file.images.is_empty()
+// {
+// sources.files.push(wim_file);
+// }
+// if let Ok(wim_file) = parse_wim_file("./24H2.wim") {
+// sources.files.push(wim_file);
+// }
+// dbg!(&sources);
+// sources.files.iter().for_each(|f| {
+// println!("-- {} --", f.path.to_string_lossy());
+// f.images.iter().for_each(|i| {
+// println!("* {i}");
+// });
+// println!();
+// });
+//
+// Ok(())
+// }