Compare commits
No commits in common. "9919a993a010f253fd1c507bcf7c25580b15ca9b" and "7e12223344238ece92a1f703d22a89c705afc70a" have entirely different histories.
9919a993a0
...
7e12223344
24 changed files with 31 additions and 2428 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
|
@ -689,7 +689,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2661,15 +2661,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.23.0"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.3",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2933,16 +2933,6 @@ version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tui-input"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19"
|
|
||||||
dependencies = [
|
|
||||||
"ratatui",
|
|
||||||
"unicode-width 0.2.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
|
|
@ -3136,30 +3126,6 @@ dependencies = [
|
||||||
"wit-bindgen-rt",
|
"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",
|
|
||||||
"tui-input",
|
|
||||||
"vergen-gix",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
"xml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -3210,12 +3176,6 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
@ -3234,15 +3194,6 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.61.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
|
|
@ -3403,12 +3354,6 @@ version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xml"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "838dd679b10a4180431ce7c2caa6e5585a7c8f63154c19ae99345126572e80cc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust2"
|
name = "yaml-rust2"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@
|
||||||
# along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
# along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
members = ["core", "boot_diags", "deja_vu", "pe_menu"]
|
||||||
default-members = ["core", "boot_diags", "deja_vu", "pe_menu", "win_installer"]
|
default-members = ["core", "boot_diags", "deja_vu", "pe_menu"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
|
|
|
||||||
|
|
@ -657,11 +657,7 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::PreClone
|
| Mode::PreClone
|
||||||
| Mode::PostClone
|
| Mode::PostClone
|
||||||
| Mode::ScanWinSources
|
| Mode::SelectTableType => {
|
||||||
| Mode::SelectTableType
|
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetUserName => {
|
|
||||||
panic!("This shouldn't happen?")
|
panic!("This shouldn't happen?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -774,11 +770,7 @@ fn build_left_items(app: &App) -> Action {
|
||||||
| Mode::Confirm
|
| Mode::Confirm
|
||||||
| Mode::PreClone
|
| Mode::PreClone
|
||||||
| Mode::Clone
|
| Mode::Clone
|
||||||
| Mode::PostClone
|
| Mode::PostClone => {
|
||||||
| Mode::ScanWinSources
|
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetUserName => {
|
|
||||||
panic!("This shouldn't happen?")
|
panic!("This shouldn't happen?")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,2 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
pub mod logview;
|
pub mod logview;
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,3 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use core::system::disk::PartitionTableType;
|
use core::system::disk::PartitionTableType;
|
||||||
use core::tasks::Tasks;
|
use core::tasks::Tasks;
|
||||||
|
|
|
||||||
|
|
@ -188,44 +188,5 @@
|
||||||
"<Ctrl-c>": "Quit",
|
"<Ctrl-c>": "Quit",
|
||||||
"<Ctrl-z>": "Suspend"
|
"<Ctrl-z>": "Suspend"
|
||||||
},
|
},
|
||||||
"ScanWinSources": {
|
|
||||||
"<Enter>": "Process",
|
|
||||||
"<b>": "FindWimBackups",
|
|
||||||
"<n>": "FindWimNetwork",
|
|
||||||
"<q>": "Quit",
|
|
||||||
"<Ctrl-d>": "Quit",
|
|
||||||
"<Ctrl-c>": "Quit",
|
|
||||||
"<Ctrl-z>": "Suspend"
|
|
||||||
},
|
|
||||||
"SelectWinSource": {
|
|
||||||
"<Enter>": "Process",
|
|
||||||
"<Up>": "KeyUp",
|
|
||||||
"<Down>": "KeyDown",
|
|
||||||
"<b>": "PrevScreen",
|
|
||||||
"<q>": "Quit",
|
|
||||||
"<Ctrl-d>": "Quit",
|
|
||||||
"<Ctrl-c>": "Quit",
|
|
||||||
"<Ctrl-z>": "Suspend"
|
|
||||||
},
|
|
||||||
"SelectWinImage": {
|
|
||||||
"<Enter>": "Process",
|
|
||||||
"<Up>": "KeyUp",
|
|
||||||
"<Down>": "KeyDown",
|
|
||||||
"<b>": "PrevScreen",
|
|
||||||
"<q>": "Quit",
|
|
||||||
"<Ctrl-d>": "Quit",
|
|
||||||
"<Ctrl-c>": "Quit",
|
|
||||||
"<Ctrl-z>": "Suspend"
|
|
||||||
},
|
|
||||||
"SetUserName": {
|
|
||||||
"<Esc>": "PrevScreen",
|
|
||||||
"<Ctrl-d>": "Quit",
|
|
||||||
"<Ctrl-c>": "Quit",
|
|
||||||
"<Ctrl-z>": "Suspend"
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"network_server": "SERVER",
|
|
||||||
"network_share": "SHARE",
|
|
||||||
"network_user": "USER",
|
|
||||||
"network_pass": "PASS"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
|
||||||
<settings pass="disabled">
|
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<UserData>
|
|
||||||
<ProductKey>
|
|
||||||
<Key />
|
|
||||||
</ProductKey>
|
|
||||||
</UserData>
|
|
||||||
<RunSynchronous>
|
|
||||||
<RunSynchronousCommand wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
|
|
||||||
</RunSynchronousCommand>
|
|
||||||
<RunSynchronousCommand wcm:action="add">
|
|
||||||
<Order>2</Order>
|
|
||||||
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>
|
|
||||||
</RunSynchronousCommand>
|
|
||||||
<RunSynchronousCommand wcm:action="add">
|
|
||||||
<Order>3</Order>
|
|
||||||
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>
|
|
||||||
</RunSynchronousCommand>
|
|
||||||
</RunSynchronous>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
<settings pass="specialize">
|
|
||||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<RunSynchronous>
|
|
||||||
<RunSynchronousCommand wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>
|
|
||||||
</RunSynchronousCommand>
|
|
||||||
</RunSynchronous>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
<settings pass="oobeSystem">
|
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<OOBE>
|
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
|
||||||
</OOBE>
|
|
||||||
<UserAccounts>
|
|
||||||
<LocalAccounts>
|
|
||||||
<LocalAccount wcm:action="add">
|
|
||||||
<Name>NEWUSERNAME</Name>
|
|
||||||
<DisplayName>NEWUSERNAME</DisplayName>
|
|
||||||
<Group>Administrators;Power Users</Group>
|
|
||||||
<Password>
|
|
||||||
<Value>UABhAHMAcwB3AG8AcgBkAA==</Value>
|
|
||||||
<PlainText>false</PlainText>
|
|
||||||
</Password>
|
|
||||||
</LocalAccount>
|
|
||||||
</LocalAccounts>
|
|
||||||
</UserAccounts>
|
|
||||||
<FirstLogonCommands>
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<CommandLine>net user "NEWUSERNAME" /expires:never</CommandLine>
|
|
||||||
</SynchronousCommand>
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>2</Order>
|
|
||||||
<CommandLine>net user "NEWUSERNAME" /passwordchg:yes</CommandLine>
|
|
||||||
</SynchronousCommand>
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>3</Order>
|
|
||||||
<CommandLine>net user "NEWUSERNAME" /passwordreq:no</CommandLine>
|
|
||||||
</SynchronousCommand>
|
|
||||||
</FirstLogonCommands>
|
|
||||||
</component>
|
|
||||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<InputLocale>00000409</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UILanguageFallback></UILanguageFallback>
|
|
||||||
</component>
|
|
||||||
<component name="Microsoft-Windows-SecureStartup-FilterDriver" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<PreventDeviceEncryption>true</PreventDeviceEncryption>
|
|
||||||
</component>
|
|
||||||
<component name="Microsoft-Windows-EnhancedStorage-Adm" processorArchitecture="amd64" language="neutral" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS">
|
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
</unattend>
|
|
||||||
|
|
@ -55,10 +55,6 @@ pub enum Action {
|
||||||
OpenTerminal,
|
OpenTerminal,
|
||||||
Restart,
|
Restart,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
// App (Win-Installer)
|
|
||||||
FindWimBackups,
|
|
||||||
FindWimNetwork,
|
|
||||||
SetUserName(String),
|
|
||||||
// Screens
|
// Screens
|
||||||
DismissPopup,
|
DismissPopup,
|
||||||
DisplayPopup(PopupType, String),
|
DisplayPopup(PopupType, String),
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use ratatui::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{action::Action, config::Config};
|
use crate::{action::Action, config::Config};
|
||||||
|
|
@ -65,7 +64,6 @@ impl Component for Popup {
|
||||||
match action {
|
match action {
|
||||||
Action::DismissPopup => self.popup_text.clear(),
|
Action::DismissPopup => self.popup_text.clear(),
|
||||||
Action::DisplayPopup(new_type, new_text) => {
|
Action::DisplayPopup(new_type, new_text) => {
|
||||||
info!("Show Popup ({new_type}): {new_text}");
|
|
||||||
self.popup_type = new_type;
|
self.popup_type = new_type;
|
||||||
self.popup_text = format!("\n{new_text}");
|
self.popup_text = format!("\n{new_text}");
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +111,6 @@ pub fn fortune() -> String {
|
||||||
7 => "TIP OF THE DAY\n\n\nNever go full Snappy!",
|
7 => "TIP OF THE DAY\n\n\nNever go full Snappy!",
|
||||||
8 => "WORDS OF WISDOM\n\n\n\nIt’s not DNS,\n\nThere’s no way it’s DNS,\n\nIt was DNS.",
|
8 => "WORDS OF WISDOM\n\n\n\nIt’s not DNS,\n\nThere’s no way it’s DNS,\n\nIt was DNS.",
|
||||||
9 => "HAL 9000\n\n\n\"I'm sorry Dave, I'm afraid I can't do that.\"",
|
9 => "HAL 9000\n\n\n\"I'm sorry Dave, I'm afraid I can't do that.\"",
|
||||||
10 => "\n\n\nIt's now safe to turn off your computer.",
|
|
||||||
_ => "COMPLETE\n\n\nThank you for using this tool!",
|
_ => "COMPLETE\n\n\nThank you for using this tool!",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,6 @@ pub struct Config {
|
||||||
pub keybindings: KeyBindings,
|
pub keybindings: KeyBindings,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub styles: Styles,
|
pub styles: Styles,
|
||||||
#[serde(default)]
|
|
||||||
pub network_server: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub network_share: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub network_user: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub network_pass: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PROJECT_NAME: &str = "DEJA-VU";
|
pub static PROJECT_NAME: &str = "DEJA-VU";
|
||||||
|
|
@ -84,14 +76,16 @@ impl Config {
|
||||||
let config_dir = get_config_dir();
|
let config_dir = get_config_dir();
|
||||||
let mut builder = config::Config::builder()
|
let mut builder = config::Config::builder()
|
||||||
.set_default("app_title", default_config.app_title.as_str())?
|
.set_default("app_title", default_config.app_title.as_str())?
|
||||||
.set_default("clone_app_path", default_config.app_title.as_str())?
|
.set_default(
|
||||||
.set_default("conemu_path", default_config.app_title.as_str())?
|
"clone_app_path",
|
||||||
|
String::from("C:\\Program Files\\Some Clone Tool\\app.exe"),
|
||||||
|
)?
|
||||||
|
.set_default(
|
||||||
|
"conemu_path",
|
||||||
|
String::from("C:\\Program Files\\ConEmu\\ConEmu64.exe"),
|
||||||
|
)?
|
||||||
.set_default("config_dir", config_dir.to_str().unwrap())?
|
.set_default("config_dir", config_dir.to_str().unwrap())?
|
||||||
.set_default("data_dir", data_dir.to_str().unwrap())?
|
.set_default("data_dir", data_dir.to_str().unwrap())?;
|
||||||
.set_default("network_server", default_config.app_title.as_str())?
|
|
||||||
.set_default("network_share", default_config.app_title.as_str())?
|
|
||||||
.set_default("network_user", default_config.app_title.as_str())?
|
|
||||||
.set_default("network_pass", default_config.app_title.as_str())?;
|
|
||||||
|
|
||||||
let config_files = [
|
let config_files = [
|
||||||
("config.json5", config::FileFormat::Json5),
|
("config.json5", config::FileFormat::Json5),
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,6 @@ pub fn get_disk_description_right(
|
||||||
line_colors,
|
line_colors,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if disk.parts_description.is_empty() {
|
|
||||||
description.push(DVLine {
|
|
||||||
line_parts: vec![String::from("-None-")],
|
|
||||||
line_colors: vec![Color::Reset],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,6 @@ pub enum Mode {
|
||||||
Clone,
|
Clone,
|
||||||
SelectParts,
|
SelectParts,
|
||||||
PostClone,
|
PostClone,
|
||||||
// Windows Installer
|
|
||||||
ScanWinSources,
|
|
||||||
SelectWinSource,
|
|
||||||
SelectWinImage,
|
|
||||||
SetUserName,
|
|
||||||
// WinPE
|
// WinPE
|
||||||
PEMenu,
|
PEMenu,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,6 @@ use crate::system::disk::{
|
||||||
|
|
||||||
static DEFAULT_MAX_DISKS: usize = 8;
|
static DEFAULT_MAX_DISKS: usize = 8;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum FormatUseCase {
|
|
||||||
ApplyWimImage,
|
|
||||||
Clone,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RegexList {
|
pub struct RegexList {
|
||||||
detail_all_disks: OnceLock<Regex>,
|
detail_all_disks: OnceLock<Regex>,
|
||||||
detail_disk: OnceLock<Regex>,
|
detail_disk: OnceLock<Regex>,
|
||||||
|
|
@ -211,11 +205,7 @@ pub fn get_partitions(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build_dest_format_script(
|
pub fn build_dest_format_script(disk_id: usize, part_type: &PartitionTableType) -> String {
|
||||||
disk_id: usize,
|
|
||||||
part_type: &PartitionTableType,
|
|
||||||
format_use_case: FormatUseCase,
|
|
||||||
) -> String {
|
|
||||||
let disk_id = format!("{disk_id}");
|
let disk_id = format!("{disk_id}");
|
||||||
let mut script = vec!["automount enable noerr", "select disk {disk_id}", "clean"];
|
let mut script = vec!["automount enable noerr", "select disk {disk_id}", "clean"];
|
||||||
match part_type {
|
match part_type {
|
||||||
|
|
@ -231,10 +221,6 @@ pub fn build_dest_format_script(
|
||||||
script.push("format fs=ntfs quick label=System");
|
script.push("format fs=ntfs quick label=System");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if format_use_case == FormatUseCase::ApplyWimImage {
|
|
||||||
script.push("create partition primary");
|
|
||||||
script.push("format fs=ntfs quick label=Windows");
|
|
||||||
}
|
|
||||||
script.join("\r\n").replace("{disk_id}", &disk_id)
|
script.join("\r\n").replace("{disk_id}", &disk_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@ use core::{
|
||||||
line::{DVLine, get_disk_description_right, get_part_description},
|
line::{DVLine, get_disk_description_right, get_part_description},
|
||||||
state::Mode,
|
state::Mode,
|
||||||
system::{
|
system::{
|
||||||
boot,
|
boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script,
|
||||||
cpu::get_cpu_name,
|
|
||||||
disk::PartitionTableType,
|
|
||||||
diskpart::{FormatUseCase, build_dest_format_script},
|
|
||||||
drivers,
|
drivers,
|
||||||
},
|
},
|
||||||
tasks::{Task, TaskResult, TaskType, Tasks},
|
tasks::{Task, TaskResult, TaskType, Tasks},
|
||||||
|
|
@ -132,11 +129,7 @@ impl App {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinSources
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,11 +154,7 @@ impl App {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinSources
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if new_mode == self.cur_mode {
|
if new_mode == self.cur_mode {
|
||||||
|
|
@ -212,8 +201,7 @@ impl App {
|
||||||
&& let Some(disk) = disk_list.get(disk_index)
|
&& let Some(disk) = disk_list.get(disk_index)
|
||||||
{
|
{
|
||||||
let table_type = self.state.table_type.clone().unwrap();
|
let table_type = self.state.table_type.clone().unwrap();
|
||||||
let diskpart_script =
|
let diskpart_script = build_dest_format_script(disk.id, &table_type);
|
||||||
build_dest_format_script(disk.id, &table_type, FormatUseCase::Clone);
|
|
||||||
self.tasks.add(TaskType::Diskpart(diskpart_script));
|
self.tasks.add(TaskType::Diskpart(diskpart_script));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,8 +461,9 @@ impl App {
|
||||||
self.set_mode(new_mode)?;
|
self.set_mode(new_mode)?;
|
||||||
self.action_tx
|
self.action_tx
|
||||||
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?;
|
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?;
|
||||||
self.action_tx.send(build_left_items(self))?;
|
self.action_tx.send(build_left_items(self, self.cur_mode))?;
|
||||||
self.action_tx.send(build_right_items(self))?;
|
self.action_tx
|
||||||
|
.send(build_right_items(self, self.cur_mode))?;
|
||||||
match new_mode {
|
match new_mode {
|
||||||
Mode::SelectTableType | Mode::Confirm => {
|
Mode::SelectTableType | Mode::Confirm => {
|
||||||
// Select source/dest disks
|
// Select source/dest disks
|
||||||
|
|
@ -645,20 +634,16 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinSources
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_left_items(app: &App) -> Action {
|
fn build_left_items(app: &App, cur_mode: Mode) -> Action {
|
||||||
let select_type: SelectionType;
|
let select_type: SelectionType;
|
||||||
let title: String;
|
let title: String;
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let mut labels: Vec<String> = Vec::new();
|
let mut labels: Vec<String> = Vec::new();
|
||||||
match app.cur_mode {
|
match cur_mode {
|
||||||
Mode::Home => {
|
Mode::Home => {
|
||||||
select_type = SelectionType::Loop;
|
select_type = SelectionType::Loop;
|
||||||
title = String::from("Home");
|
title = String::from("Home");
|
||||||
|
|
@ -722,20 +707,16 @@ fn build_left_items(app: &App) -> Action {
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::Process
|
| Mode::Process
|
||||||
| Mode::ScanWinSources
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
| Mode::SelectWinSource
|
|
||||||
| Mode::SelectWinImage
|
|
||||||
| Mode::SetBootMode
|
|
||||||
| Mode::SetUserName => panic!("This shouldn't happen?"),
|
|
||||||
};
|
};
|
||||||
Action::UpdateLeft(title, labels, items, select_type)
|
Action::UpdateLeft(title, labels, items, select_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_right_items(app: &App) -> Action {
|
fn build_right_items(app: &App, cur_mode: Mode) -> Action {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
||||||
let mut start_index = 0;
|
let mut start_index = 0;
|
||||||
match app.cur_mode {
|
match cur_mode {
|
||||||
Mode::InstallDrivers => {
|
Mode::InstallDrivers => {
|
||||||
items.push(vec![DVLine {
|
items.push(vec![DVLine {
|
||||||
line_parts: vec![String::from("CPU")],
|
line_parts: vec![String::from("CPU")],
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
[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"
|
|
||||||
windows-sys = { version = "0.61.1", features = ["Win32_NetworkManagement_WNet"] }
|
|
||||||
xml = "1.1.0"
|
|
||||||
tui-input = "0.14.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
anyhow = "1.0.86"
|
|
||||||
vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
pub mod set_username;
|
|
||||||
pub mod wim_scan;
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use core::{action::Action, components::Component, config::Config, state::Mode, tui::Event};
|
|
||||||
use crossterm::event::KeyCode;
|
|
||||||
use ratatui::{
|
|
||||||
prelude::*,
|
|
||||||
widgets::{Block, Borders, Clear, Paragraph},
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
|
||||||
use tui_input::{Input, InputRequest};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct InputUsername {
|
|
||||||
command_tx: Option<UnboundedSender<Action>>,
|
|
||||||
config: Config,
|
|
||||||
input: Input,
|
|
||||||
mode: Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputUsername {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
input: Input::new(String::from("")),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for InputUsername {
|
|
||||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
|
||||||
self.command_tx = Some(tx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
|
||||||
self.config = config;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> {
|
|
||||||
if self.mode != Mode::SetUserName {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let action = match event {
|
|
||||||
Some(Event::Key(key_event)) => match key_event.code {
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
self.input.handle(InputRequest::DeletePrevChar);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
let ok_chars: Vec<char> = vec![' ', '-', '_'];
|
|
||||||
if c.is_ascii_alphanumeric() || ok_chars.contains(&c) {
|
|
||||||
self.input.handle(InputRequest::InsertChar(c));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let username = self.input.value();
|
|
||||||
Some(Action::SetUserName(String::from(username)))
|
|
||||||
}
|
|
||||||
KeyCode::Esc => Some(Action::SetMode(Mode::Home)),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Some(Event::Mouse(_)) => None,
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
Ok(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
|
||||||
match action {
|
|
||||||
Action::SetMode(mode) => self.mode = mode.clone(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
||||||
if self.mode != Mode::SetUserName {
|
|
||||||
// Bail early
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set areas
|
|
||||||
let [_, center_area, _] = Layout::horizontal([
|
|
||||||
Constraint::Length(1),
|
|
||||||
Constraint::Min(1),
|
|
||||||
Constraint::Length(1),
|
|
||||||
])
|
|
||||||
.areas(area);
|
|
||||||
let [_, input_area, _] = Layout::vertical([
|
|
||||||
Constraint::Length(1),
|
|
||||||
Constraint::Length(3),
|
|
||||||
Constraint::Length(1),
|
|
||||||
])
|
|
||||||
.areas(center_area);
|
|
||||||
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
let outer_block = Block::bordered().cyan().bold();
|
|
||||||
frame.render_widget(outer_block, area);
|
|
||||||
|
|
||||||
// Input Box
|
|
||||||
let width = input_area.width.max(3) - 3; // keep 2 for borders and 1 for cursor
|
|
||||||
let scroll = self.input.visual_scroll(width as usize);
|
|
||||||
let input = Paragraph::new(self.input.value())
|
|
||||||
.scroll((0, scroll as u16))
|
|
||||||
.white()
|
|
||||||
.block(Block::new().borders(Borders::ALL).title("Enter Username"));
|
|
||||||
frame.render_widget(input, input_area);
|
|
||||||
|
|
||||||
// Ratatui hides the cursor unless it's explicitly set. Position the cursor past the
|
|
||||||
// end of the input text and one line down from the border to the input line
|
|
||||||
let x = self.input.visual_cursor().max(scroll) - scroll + 1;
|
|
||||||
frame.set_cursor_position((input_area.x + x as u16, input_area.y + 1));
|
|
||||||
|
|
||||||
// Done
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use core::{action::Action, components::Component, config::Config, state::Mode};
|
|
||||||
use std::{
|
|
||||||
iter::zip,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use crossterm::event::KeyEvent;
|
|
||||||
use ratatui::{
|
|
||||||
prelude::*,
|
|
||||||
widgets::{Block, Borders, Clear, List, ListItem, Padding, Paragraph},
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
|
||||||
|
|
||||||
use crate::wim::WimSources;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WimScan {
|
|
||||||
command_tx: Option<UnboundedSender<Action>>,
|
|
||||||
config: Config,
|
|
||||||
mode: Mode,
|
|
||||||
scan_network: bool,
|
|
||||||
wim_sources: Arc<Mutex<WimSources>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WimScan {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(wim_sources: Arc<Mutex<WimSources>>) -> Self {
|
|
||||||
let wim_sources = wim_sources.clone();
|
|
||||||
Self {
|
|
||||||
wim_sources,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for WimScan {
|
|
||||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
|
||||||
let _ = key; // to appease clippy
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
|
||||||
self.command_tx = Some(tx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
|
||||||
self.config = config;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
|
||||||
match action {
|
|
||||||
Action::FindWimNetwork => self.scan_network = true,
|
|
||||||
Action::SetMode(new_mode) => {
|
|
||||||
self.mode = new_mode;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
||||||
if self.mode != Mode::ScanWinSources {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
frame.render_widget(Clear, area);
|
|
||||||
|
|
||||||
// Prep
|
|
||||||
let [left, right] = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
||||||
.areas(area);
|
|
||||||
let [left_title, left_body] = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
|
||||||
.areas(left);
|
|
||||||
let [right_title, right_body] = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Length(1), Constraint::Min(1)])
|
|
||||||
.areas(right);
|
|
||||||
|
|
||||||
// Titles
|
|
||||||
let titles = vec![
|
|
||||||
Paragraph::new(Line::from("Local").centered())
|
|
||||||
.block(Block::default().borders(Borders::NONE)),
|
|
||||||
Paragraph::new(Line::from("Network").centered())
|
|
||||||
.block(Block::default().borders(Borders::NONE)),
|
|
||||||
];
|
|
||||||
for (title, area) in zip(titles, [left_title, right_title]) {
|
|
||||||
frame.render_widget(title, area);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WIM Info
|
|
||||||
if let Ok(wim_sources) = self.wim_sources.lock() {
|
|
||||||
// Local
|
|
||||||
let mut left_list = Vec::new();
|
|
||||||
if wim_sources.thread_local.is_some() {
|
|
||||||
left_list.push(ListItem::new("Scanning..."));
|
|
||||||
} else {
|
|
||||||
left_list.extend(
|
|
||||||
wim_sources
|
|
||||||
.local
|
|
||||||
.iter()
|
|
||||||
.map(|wimfile| ListItem::new(format!("{}\n\n", wimfile.path))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let left_list = List::new(left_list).block(
|
|
||||||
Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.padding(Padding::new(1, 1, 1, 1)),
|
|
||||||
);
|
|
||||||
frame.render_widget(left_list, left_body);
|
|
||||||
|
|
||||||
// Network
|
|
||||||
let mut right_list = Vec::new();
|
|
||||||
if wim_sources.thread_network.is_some() {
|
|
||||||
right_list.push(ListItem::new("Scanning..."));
|
|
||||||
} else {
|
|
||||||
right_list.extend(wim_sources.network.iter().map(|wimfile| {
|
|
||||||
ListItem::new(format!(
|
|
||||||
"{}\n\n",
|
|
||||||
wimfile.path.split("\\").last().unwrap_or(&wimfile.path)
|
|
||||||
))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
let right_list = List::new(right_list).block(
|
|
||||||
Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.padding(Padding::new(1, 1, 1, 1)),
|
|
||||||
);
|
|
||||||
frame.render_widget(right_list, right_body);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use clap::Parser;
|
|
||||||
use color_eyre::Result;
|
|
||||||
|
|
||||||
use crate::app::App;
|
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod components;
|
|
||||||
mod net;
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
//#![windows_subsystem = "windows"]
|
|
||||||
use std::ffi::CString;
|
|
||||||
|
|
||||||
use windows_sys::Win32::Foundation::NO_ERROR;
|
|
||||||
use windows_sys::Win32::NetworkManagement::WNet;
|
|
||||||
|
|
||||||
fn to_cstr(s: &str) -> CString {
|
|
||||||
CString::new(s).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_network_share(
|
|
||||||
server: &str,
|
|
||||||
share: &str,
|
|
||||||
username: &str,
|
|
||||||
password: &str,
|
|
||||||
) -> Result<(), u32> {
|
|
||||||
let remote_name = to_cstr(&format!("\\\\{server}\\{share}"));
|
|
||||||
|
|
||||||
// init resources
|
|
||||||
let mut resources = WNet::NETRESOURCEA {
|
|
||||||
dwDisplayType: WNet::RESOURCEDISPLAYTYPE_SHAREADMIN,
|
|
||||||
dwScope: WNet::RESOURCE_GLOBALNET,
|
|
||||||
dwType: WNet::RESOURCETYPE_DISK,
|
|
||||||
dwUsage: WNet::RESOURCEUSAGE_ALL,
|
|
||||||
lpComment: std::ptr::null_mut(),
|
|
||||||
lpLocalName: std::ptr::null_mut(), // PUT a volume here if you want to mount as a windows volume
|
|
||||||
lpProvider: std::ptr::null_mut(),
|
|
||||||
lpRemoteName: remote_name.as_c_str().as_ptr() as *mut u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
let username = format!("{server}\\{username}");
|
|
||||||
let username = to_cstr(&username);
|
|
||||||
let password = to_cstr(password);
|
|
||||||
|
|
||||||
// mount
|
|
||||||
let result = unsafe {
|
|
||||||
let username_ptr = username.as_ptr();
|
|
||||||
let password_ptr = password.as_ptr();
|
|
||||||
WNet::WNetAddConnection2A(
|
|
||||||
&mut resources as *mut WNet::NETRESOURCEA,
|
|
||||||
password_ptr as *const u8,
|
|
||||||
username_ptr as *const u8,
|
|
||||||
//WNet::CONNECT_INTERACTIVE, // Interactive will show a system dialog in case credentials are wrong to retry with the password. Put 0 if you don't want it
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if result == NO_ERROR {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
fs::read_dir,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use core::{
|
|
||||||
config::Config,
|
|
||||||
system::{
|
|
||||||
disk::{Disk, PartitionTableType},
|
|
||||||
drivers,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
net::connect_network_share,
|
|
||||||
wim::{WimFile, WimSources, parse_wim_file},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum ScanType {
|
|
||||||
GeneralWimFiles, // Includes Windows installer WIMs
|
|
||||||
WindowsInstallers,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct State {
|
|
||||||
pub config: Config,
|
|
||||||
pub disk_index_dest: Option<usize>,
|
|
||||||
pub disk_list: Arc<Mutex<Vec<Disk>>>,
|
|
||||||
pub driver: Option<drivers::Driver>,
|
|
||||||
pub driver_list: Vec<drivers::Driver>,
|
|
||||||
pub table_type: Option<PartitionTableType>,
|
|
||||||
pub username: Option<String>,
|
|
||||||
pub wim_file_index: Option<usize>,
|
|
||||||
pub wim_image_index: Option<usize>,
|
|
||||||
pub wim_sources: Arc<Mutex<WimSources>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn new(config: Config, disk_list: Arc<Mutex<Vec<Disk>>>) -> Self {
|
|
||||||
let wim_sources = Arc::new(Mutex::new(WimSources::new()));
|
|
||||||
State {
|
|
||||||
config,
|
|
||||||
disk_list,
|
|
||||||
wim_sources,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_all(&mut self) {
|
|
||||||
self.wim_file_index = None;
|
|
||||||
self.wim_image_index = None;
|
|
||||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
|
||||||
sources.reset_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_local(&mut self) {
|
|
||||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
|
||||||
sources.reset_local();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_network(&mut self) {
|
|
||||||
if let Ok(mut sources) = self.wim_sources.lock() {
|
|
||||||
sources.reset_network();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_drivers(&mut self) {
|
|
||||||
self.driver_list = drivers::scan();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_wim_local(&mut self, scan_type: ScanType) {
|
|
||||||
let disk_list_arc = self.disk_list.clone();
|
|
||||||
let wim_sources_arc = self.wim_sources.clone();
|
|
||||||
let wim_sources_arc_inner = self.wim_sources.clone();
|
|
||||||
if let Ok(mut wim_sources) = wim_sources_arc.lock()
|
|
||||||
&& wim_sources.thread_local.is_none()
|
|
||||||
{
|
|
||||||
wim_sources.thread_local = Some(tokio::task::spawn(async move {
|
|
||||||
scan_local_drives(disk_list_arc, wim_sources_arc_inner, scan_type);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_wim_network(&mut self) {
|
|
||||||
let wim_sources_arc = self.wim_sources.clone();
|
|
||||||
let wim_sources_arc_inner = self.wim_sources.clone();
|
|
||||||
if let Ok(mut wim_sources) = wim_sources_arc.lock()
|
|
||||||
&& wim_sources.thread_network.is_none()
|
|
||||||
{
|
|
||||||
let config = self.config.clone();
|
|
||||||
wim_sources.thread_network = Some(tokio::task::spawn(async move {
|
|
||||||
scan_network_share(config, wim_sources_arc_inner);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_subfolders(path_str: &str) -> Vec<String> {
|
|
||||||
if let Ok(read_dir) = read_dir(path_str) {
|
|
||||||
read_dir
|
|
||||||
.filter_map(|item| item.ok())
|
|
||||||
.map(|item| item.path().to_string_lossy().into_owned())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
// TODO: Use better error handling here?
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_local_drives(
|
|
||||||
disk_list_arc: Arc<Mutex<Vec<Disk>>>,
|
|
||||||
wim_sources_arc: Arc<Mutex<WimSources>>,
|
|
||||||
scan_type: ScanType,
|
|
||||||
) {
|
|
||||||
let mut to_check: Vec<String> = Vec::new();
|
|
||||||
let mut wim_files: Vec<WimFile> = Vec::new();
|
|
||||||
|
|
||||||
// Get drive letters
|
|
||||||
if let Ok(disk_list) = disk_list_arc.lock() {
|
|
||||||
disk_list.iter().for_each(|d| {
|
|
||||||
d.parts.iter().for_each(|p| {
|
|
||||||
if !p.letter.is_empty() {
|
|
||||||
match scan_type {
|
|
||||||
ScanType::GeneralWimFiles => {
|
|
||||||
to_check.append(&mut get_subfolders(&format!("{}:\\", &p.letter)));
|
|
||||||
}
|
|
||||||
ScanType::WindowsInstallers => {
|
|
||||||
to_check.push(format!("{}:\\Images", &p.letter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan drives
|
|
||||||
to_check.iter().for_each(|scan_path| {
|
|
||||||
let is_backup = !scan_path.ends_with("\\Images");
|
|
||||||
if let Ok(read_dir) = read_dir(scan_path) {
|
|
||||||
read_dir.for_each(|item| {
|
|
||||||
if let Ok(item) = item
|
|
||||||
&& item.file_name().to_string_lossy().ends_with(".wim")
|
|
||||||
&& let Some(path_str) = item.path().to_str()
|
|
||||||
&& let Ok(new_source) = parse_wim_file(path_str, is_backup)
|
|
||||||
{
|
|
||||||
wim_files.push(new_source);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Done
|
|
||||||
wim_files.sort();
|
|
||||||
if let Ok(mut wim_sources) = wim_sources_arc.lock() {
|
|
||||||
wim_files
|
|
||||||
.into_iter()
|
|
||||||
.for_each(|file| wim_sources.add_local(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_network_share(config: Config, wim_sources_arc: Arc<Mutex<WimSources>>) {
|
|
||||||
let result = connect_network_share(
|
|
||||||
&config.network_server,
|
|
||||||
&config.network_share,
|
|
||||||
&config.network_user,
|
|
||||||
&config.network_pass,
|
|
||||||
);
|
|
||||||
let mut wim_files: Vec<WimFile> = Vec::new();
|
|
||||||
|
|
||||||
// Connect to share
|
|
||||||
if result.is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan share
|
|
||||||
let share_dir = format!("\\\\{}\\{}", &config.network_server, &config.network_share);
|
|
||||||
if let Ok(read_dir) = read_dir(share_dir) {
|
|
||||||
read_dir.for_each(|item| {
|
|
||||||
if let Ok(item) = item
|
|
||||||
&& item.file_name().to_string_lossy().ends_with(".wim")
|
|
||||||
&& let Some(path_str) = item.path().to_str()
|
|
||||||
&& let Ok(new_source) = parse_wim_file(path_str, false)
|
|
||||||
// Assuming all network sources are installers
|
|
||||||
{
|
|
||||||
wim_files.push(new_source);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
wim_files.sort();
|
|
||||||
if let Ok(mut wim_sources) = wim_sources_arc.lock() {
|
|
||||||
wim_files
|
|
||||||
.into_iter()
|
|
||||||
.for_each(|file| wim_sources.add_network(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
collections::HashMap,
|
|
||||||
env, fmt,
|
|
||||||
fs::File,
|
|
||||||
io::BufReader,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
|
||||||
sync::LazyLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use tokio::task::JoinHandle;
|
|
||||||
use xml::reader::{EventReader, XmlEvent};
|
|
||||||
|
|
||||||
use core::system::disk::bytes_to_string;
|
|
||||||
|
|
||||||
const UNATTEND_XML: &str = include_str!("../../config/unattend.xml");
|
|
||||||
|
|
||||||
static WIMINFO_EXE: LazyLock<String> = LazyLock::new(|| {
|
|
||||||
let program_files =
|
|
||||||
PathBuf::from(env::var("PROGRAMFILES").expect("Failed to resolve %PROGRAMFILES%"));
|
|
||||||
program_files
|
|
||||||
.join("wimlib/wiminfo.cmd")
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned()
|
|
||||||
});
|
|
||||||
|
|
||||||
static WIN_BUILDS: LazyLock<HashMap<&str, &str>> = 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(Clone, Debug)]
|
|
||||||
pub struct WimFile {
|
|
||||||
pub path: String,
|
|
||||||
pub images: Vec<WimImage>,
|
|
||||||
pub is_backup: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WimFile {
|
|
||||||
pub fn summary(&self) -> String {
|
|
||||||
let mut s = format!("{self}");
|
|
||||||
self.images.iter().for_each(|image| {
|
|
||||||
let image = format!("\n\t\t{image}");
|
|
||||||
s.push_str(&image);
|
|
||||||
});
|
|
||||||
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for WimFile {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.path.split("\\").last().unwrap_or(&self.path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for WimFile {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.path == other.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for WimFile {}
|
|
||||||
|
|
||||||
impl Ord for WimFile {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.path.cmp(&other.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for WimFile {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct WimImage {
|
|
||||||
pub build: String,
|
|
||||||
pub index: String,
|
|
||||||
pub name: String,
|
|
||||||
pub size: u64,
|
|
||||||
pub spbuild: String,
|
|
||||||
pub 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,
|
|
||||||
bytes_to_string(self.size)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct WimSources {
|
|
||||||
pub local: Vec<WimFile>,
|
|
||||||
pub network: Vec<WimFile>,
|
|
||||||
pub thread_local: Option<JoinHandle<()>>,
|
|
||||||
pub thread_network: Option<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WimSources {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_local(&mut self, wim_file: WimFile) {
|
|
||||||
self.local.push(wim_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_network(&mut self, wim_file: WimFile) {
|
|
||||||
self.network.push(wim_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_file(&self, index: usize) -> WimFile {
|
|
||||||
let rel_index: usize;
|
|
||||||
let num_local = self.local.len();
|
|
||||||
let mut use_local = true;
|
|
||||||
if index < num_local {
|
|
||||||
rel_index = index;
|
|
||||||
} else {
|
|
||||||
rel_index = index - num_local;
|
|
||||||
use_local = false;
|
|
||||||
};
|
|
||||||
if use_local {
|
|
||||||
self.local.get(rel_index).unwrap().clone()
|
|
||||||
} else {
|
|
||||||
self.network.get(rel_index).unwrap().clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_file_list(&self) -> Vec<WimFile> {
|
|
||||||
let mut list = self.local.clone();
|
|
||||||
list.append(&mut self.network.clone());
|
|
||||||
list
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll(&mut self) {
|
|
||||||
let thread = self.thread_local.take();
|
|
||||||
if let Some(local) = thread
|
|
||||||
&& !local.is_finished()
|
|
||||||
{
|
|
||||||
// Task still going, keep tracking
|
|
||||||
self.thread_local = Some(local);
|
|
||||||
}
|
|
||||||
let thread = self.thread_network.take();
|
|
||||||
if let Some(network) = thread
|
|
||||||
&& !network.is_finished()
|
|
||||||
{
|
|
||||||
// Task still going, keep tracking
|
|
||||||
self.thread_network = Some(network);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_all(&mut self) {
|
|
||||||
self.local.clear();
|
|
||||||
self.network.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_local(&mut self) {
|
|
||||||
self.local.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_network(&mut self) {
|
|
||||||
self.network.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen_unattend_xml(username: &str) -> String {
|
|
||||||
UNATTEND_XML.replace("NEWUSERNAME", username)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_wim_xml(wim_file: &str) -> std::io::Result<File> {
|
|
||||||
let tmp_file = NamedTempFile::new()?;
|
|
||||||
let _ = Command::new(&*WIMINFO_EXE)
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_wim_file(wim_file: &str, is_backup: bool) -> std::io::Result<WimFile> {
|
|
||||||
let mut wim_images: Vec<WimImage> = Vec::new();
|
|
||||||
if !Path::new(wim_file).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);
|
|
||||||
for e in parser {
|
|
||||||
match e {
|
|
||||||
Ok(XmlEvent::StartElement {
|
|
||||||
name, attributes, ..
|
|
||||||
}) => {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
if current_element == "NAME" {
|
|
||||||
image.name = char_data.trim().to_string();
|
|
||||||
}
|
|
||||||
if current_element == "SPBUILD" {
|
|
||||||
image.spbuild = char_data.trim().to_string();
|
|
||||||
}
|
|
||||||
if current_element == "TOTALBYTES" {
|
|
||||||
let result = char_data.trim().parse::<u64>();
|
|
||||||
if let Ok(size) = result {
|
|
||||||
image.size = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(XmlEvent::EndElement { name }) => {
|
|
||||||
if name.local_name.to_uppercase() == "IMAGE" {
|
|
||||||
if image.size == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Append image to list
|
|
||||||
if image.build.is_empty() {
|
|
||||||
image.build.push('?');
|
|
||||||
}
|
|
||||||
if image.spbuild.is_empty() {
|
|
||||||
image.spbuild.push('?');
|
|
||||||
}
|
|
||||||
if !image.name.is_empty() && !image.index.is_empty() {
|
|
||||||
wim_images.push(image.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset image
|
|
||||||
image.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let wim_file = WimFile {
|
|
||||||
path: wim_file.to_string(),
|
|
||||||
images: wim_images,
|
|
||||||
is_backup,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(wim_file)
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue