use std::fmt::{Display, Formatter};
use std::ops::RangeBounds;
use std::{env, fmt};
use crate::manifest::xml::XmlFormatter;
mod xml;
#[cfg(test)]
mod test;
#[derive(Debug)]
pub struct ManifestBuilder {
identity: Option<AssemblyIdentity>,
dependent_assemblies: Vec<AssemblyIdentity>,
compatibility: ApplicationCompatibility,
windows_settings: WindowsSettings,
requested_execution_level: Option<RequestedExecutionLevel>,
}
impl ManifestBuilder {
pub(crate) fn new(name: &str) -> Self {
ManifestBuilder {
identity: Some(AssemblyIdentity::application(name)),
dependent_assemblies: vec![AssemblyIdentity::new(
"Microsoft.Windows.Common-Controls",
[6, 0, 0, 0],
0x6595b64144ccf1df,
)],
compatibility: ApplicationCompatibility {
max_version_tested: Some(MaxVersionTested::Windows10Version1903),
supported_os: vec![
SupportedOS::Windows7,
SupportedOS::Windows8,
SupportedOS::Windows81,
SupportedOS::Windows10,
],
},
windows_settings: WindowsSettings::new(),
requested_execution_level: Some(RequestedExecutionLevel {
level: ExecutionLevel::AsInvoker,
ui_access: false,
}),
}
}
pub(crate) fn empty() -> Self {
ManifestBuilder {
identity: None,
dependent_assemblies: Vec::new(),
compatibility: ApplicationCompatibility {
max_version_tested: None,
supported_os: Vec::new(),
},
windows_settings: WindowsSettings::empty(),
requested_execution_level: None,
}
}
pub fn name(mut self, name: &str) -> Self {
match self.identity {
Some(ref mut identity) => identity.name = name.to_string(),
None => self.identity = Some(AssemblyIdentity::application_version(name, 0, 0, 0, 0)),
}
self
}
pub fn version(mut self, major: u16, minor: u16, build: u16, revision: u16) -> Self {
match self.identity {
Some(ref mut identity) => identity.version = Version(major, minor, build, revision),
None => {
self.identity = Some(AssemblyIdentity::application_version("", major, minor, build, revision));
}
}
self
}
pub fn dependency(mut self, identity: AssemblyIdentity) -> Self {
self.dependent_assemblies.push(identity);
self
}
pub fn remove_dependency(mut self, name: &str) -> Self {
self.dependent_assemblies.retain(|d| d.name != name);
self
}
pub fn max_version_tested(mut self, version: MaxVersionTested) -> Self {
self.compatibility.max_version_tested = Some(version);
self
}
pub fn remove_max_version_tested(mut self) -> Self {
self.compatibility.max_version_tested = None;
self
}
pub fn supported_os<R: RangeBounds<SupportedOS>>(mut self, os_range: R) -> Self {
use SupportedOS::*;
self.compatibility.supported_os.clear();
for os in [WindowsVista, Windows7, Windows8, Windows81, Windows10] {
if os_range.contains(&os) {
self.compatibility.supported_os.push(os);
}
}
self
}
pub fn active_code_page(mut self, code_page: ActiveCodePage) -> Self {
self.windows_settings.active_code_page = code_page;
self
}
pub fn dpi_awareness(mut self, setting: DpiAwareness) -> Self {
self.windows_settings.dpi_awareness = setting;
self
}
pub fn gdi_scaling(mut self, setting: Setting) -> Self {
self.windows_settings.gdi_scaling = setting.enabled();
self
}
pub fn heap_type(mut self, setting: HeapType) -> Self {
self.windows_settings.heap_type = setting;
self
}
pub fn long_path_aware(mut self, setting: Setting) -> Self {
self.windows_settings.long_path_aware = setting.enabled();
self
}
pub fn printer_driver_isolation(mut self, setting: Setting) -> Self {
self.windows_settings.printer_driver_isolation = setting.enabled();
self
}
pub fn scrolling_awareness(mut self, setting: ScrollingAwareness) -> Self {
self.windows_settings.scrolling_awareness = setting;
self
}
pub fn window_filtering(mut self, setting: Setting) -> Self {
self.windows_settings.disable_window_filtering = setting.disabled();
self
}
pub fn requested_execution_level(mut self, level: ExecutionLevel) -> Self {
match self.requested_execution_level {
Some(ref mut requested_execution_level) => requested_execution_level.level = level,
None => self.requested_execution_level = Some(RequestedExecutionLevel { level, ui_access: false }),
}
self
}
pub fn ui_access(mut self, access: bool) -> Self {
match self.requested_execution_level {
Some(ref mut requested_execution_level) => requested_execution_level.ui_access = access,
None => {
self.requested_execution_level = Some(RequestedExecutionLevel {
level: ExecutionLevel::AsInvoker,
ui_access: access,
})
}
}
self
}
}
impl Display for ManifestBuilder {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut w = XmlFormatter::new(f);
w.start_document()?;
let mut attrs = vec![("xmlns", "urn:schemas-microsoft-com:asm.v1")];
if !self.windows_settings.is_empty() || self.requested_execution_level.is_some() {
attrs.push(("xmlns:asmv3", "urn:schemas-microsoft-com:asm.v3"));
}
attrs.push(("manifestVersion", "1.0"));
w.start_element("assembly", &attrs)?;
if let Some(ref identity) = self.identity {
identity.xml_to(&mut w)?;
}
for d in self.dependent_assemblies.as_slice() {
w.element("dependency", &[], |w| w.element("dependentAssembly", &[], |w| d.xml_to(w)))?;
}
if !self.compatibility.is_empty() {
self.compatibility.xml_to(&mut w)?;
}
if !self.windows_settings.is_empty() {
self.windows_settings.xml_to(&mut w)?;
}
if let Some(ref requested_execution_level) = self.requested_execution_level {
requested_execution_level.xml_to(&mut w)?;
}
w.end_element("assembly")
}
}
#[derive(Debug)]
pub struct AssemblyIdentity {
r#type: AssemblyType,
name: String,
language: Option<String>,
processor_architecture: Option<AssemblyProcessorArchitecture>,
version: Version,
public_key_token: Option<PublicKeyToken>,
}
impl AssemblyIdentity {
fn application(name: &str) -> AssemblyIdentity {
let major = env::var("CARGO_PKG_VERSION_MAJOR").map_or(0, |s| s.parse().unwrap_or(0));
let minor = env::var("CARGO_PKG_VERSION_MINOR").map_or(0, |s| s.parse().unwrap_or(0));
let patch = env::var("CARGO_PKG_VERSION_PATCH").map_or(0, |s| s.parse().unwrap_or(0));
AssemblyIdentity {
r#type: AssemblyType::Win32,
name: name.to_string(),
language: None,
processor_architecture: None,
version: Version(major, minor, patch, 0),
public_key_token: None,
}
}
fn application_version(name: &str, major: u16, minor: u16, build: u16, revision: u16) -> AssemblyIdentity {
AssemblyIdentity {
r#type: AssemblyType::Win32,
name: name.to_string(),
language: None,
processor_architecture: None,
version: Version(major, minor, build, revision),
public_key_token: None,
}
}
pub fn new(name: &str, version: [u16; 4], public_key_token: u64) -> AssemblyIdentity {
AssemblyIdentity {
r#type: AssemblyType::Win32,
name: name.to_string(),
language: Some("*".to_string()),
processor_architecture: Some(AssemblyProcessorArchitecture::All),
version: Version(version[0], version[1], version[2], version[3]),
public_key_token: Some(PublicKeyToken(public_key_token)),
}
}
pub fn language(mut self, language: &str) -> Self {
self.language = Some(language.to_string());
self
}
pub fn processor_architecture(mut self, arch: AssemblyProcessorArchitecture) -> Self {
self.processor_architecture = Some(arch);
self
}
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
let version = self.version.to_string();
let public_key_token = self.public_key_token.as_ref().map(|token| token.to_string());
let mut attrs: Vec<(&str, &str)> = Vec::with_capacity(6);
if let Some(ref language) = self.language {
attrs.push(("language", language));
}
attrs.push(("name", &self.name));
if let Some(ref arch) = self.processor_architecture {
attrs.push(("processorArchitecture", arch.as_str()))
}
if let Some(ref token) = public_key_token {
attrs.push(("publicKeyToken", token));
}
attrs.push(("type", self.r#type.as_str()));
attrs.push(("version", &version));
w.empty_element("assemblyIdentity", &attrs)
}
}
#[derive(Debug)]
struct Version(u16, u16, u16, u16);
impl fmt::Display for Version {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
}
}
#[derive(Debug)]
struct PublicKeyToken(u64);
impl fmt::Display for PublicKeyToken {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum AssemblyProcessorArchitecture {
All,
X86,
Amd64,
Arm,
Arm64,
}
impl AssemblyProcessorArchitecture {
pub fn as_str(&self) -> &'static str {
match self {
Self::All => "*",
Self::X86 => "x86",
Self::Amd64 => "amd64",
Self::Arm => "arm",
Self::Arm64 => "arm64",
}
}
}
impl fmt::Display for AssemblyProcessorArchitecture {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}
#[derive(Debug)]
#[non_exhaustive]
enum AssemblyType {
Win32,
}
impl AssemblyType {
fn as_str(&self) -> &'static str {
"win32"
}
}
#[derive(Debug)]
struct ApplicationCompatibility {
max_version_tested: Option<MaxVersionTested>,
supported_os: Vec<SupportedOS>,
}
impl ApplicationCompatibility {
fn is_empty(&self) -> bool {
self.supported_os.is_empty()
}
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
w.element(
"compatibility",
&[("xmlns", "urn:schemas-microsoft-com:compatibility.v1")],
|w| {
w.element("application", &[], |w| {
if self.supported_os.contains(&SupportedOS::Windows10) {
if let Some(ref version) = self.max_version_tested {
w.empty_element("maxversiontested", &[("Id", version.as_str())])?;
}
}
for os in self.supported_os.iter() {
w.empty_element("supportedOS", &[("Id", os.as_str())])?
}
Ok(())
})
},
)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MaxVersionTested {
Windows10Version1903,
Windows10Version2004,
Windows10Version2104,
Windows11,
Windows11Version22H2,
}
impl MaxVersionTested {
pub fn as_str(&self) -> &'static str {
match self {
Self::Windows10Version1903 => "10.0.18362.1",
Self::Windows10Version2004 => "10.0.19041.0",
Self::Windows10Version2104 => "10.0.20348.0",
Self::Windows11 => "10.0.22000.194",
Self::Windows11Version22H2 => "10.0.22621.1",
}
}
}
impl Display for MaxVersionTested {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
#[non_exhaustive]
pub enum SupportedOS {
WindowsVista,
Windows7,
Windows8,
Windows81,
Windows10,
}
impl SupportedOS {
pub fn as_str(&self) -> &'static str {
match self {
Self::WindowsVista => "{e2011457-1546-43c5-a5fe-008deee3d3f0}",
Self::Windows7 => "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
Self::Windows8 => "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
Self::Windows81 => "{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
Self::Windows10 => "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}",
}
}
}
impl Display for SupportedOS {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}
static WS2005: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
static WS2011: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2011/WindowsSettings");
static WS2013: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2013/WindowsSettings");
static WS2016: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
static WS2017: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2017/WindowsSettings");
static WS2019: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2019/WindowsSettings");
static WS2020: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2020/WindowsSettings");
#[derive(Debug)]
struct WindowsSettings {
active_code_page: ActiveCodePage,
disable_window_filtering: bool,
dpi_awareness: DpiAwareness,
gdi_scaling: bool,
heap_type: HeapType,
long_path_aware: bool,
printer_driver_isolation: bool,
scrolling_awareness: ScrollingAwareness,
}
impl WindowsSettings {
fn new() -> Self {
Self {
active_code_page: ActiveCodePage::Utf8,
disable_window_filtering: false,
dpi_awareness: DpiAwareness::PerMonitorV2Only,
gdi_scaling: false,
heap_type: HeapType::LowFragmentationHeap,
long_path_aware: true,
printer_driver_isolation: true,
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
}
}
fn empty() -> Self {
Self {
active_code_page: ActiveCodePage::System,
disable_window_filtering: false,
dpi_awareness: DpiAwareness::UnawareByDefault,
gdi_scaling: false,
heap_type: HeapType::LowFragmentationHeap,
long_path_aware: false,
printer_driver_isolation: false,
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
}
}
fn is_empty(&self) -> bool {
matches!(
self,
Self {
active_code_page: ActiveCodePage::System,
disable_window_filtering: false,
dpi_awareness: DpiAwareness::UnawareByDefault,
gdi_scaling: false,
heap_type: HeapType::LowFragmentationHeap,
long_path_aware: false,
printer_driver_isolation: false,
scrolling_awareness: ScrollingAwareness::UltraHighResolution,
}
)
}
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
w.element("asmv3:application", &[], |w| {
w.element("asmv3:windowsSettings", &[], |w| {
self.active_code_page.xml_to(w)?;
if self.disable_window_filtering {
w.element("disableWindowFiltering", &[WS2011], |w| w.text("true"))?;
}
self.dpi_awareness.xml_to(w)?;
if self.gdi_scaling {
w.element("gdiScaling", &[WS2017], |w| w.text("true"))?;
}
if matches!(self.heap_type, HeapType::SegmentHeap) {
w.element("heapType", &[WS2020], |w| w.text("SegmentHeap"))?;
}
if self.long_path_aware {
w.element("longPathAware", &[WS2016], |w| w.text("true"))?;
}
if self.printer_driver_isolation {
w.element("printerDriverIsolation", &[WS2011], |w| w.text("true"))?;
}
self.scrolling_awareness.xml_to(w)
})
})
}
}
#[derive(Debug)]
pub enum Setting {
Disabled = 0,
Enabled = 1,
}
impl Setting {
fn disabled(&self) -> bool {
matches!(self, Setting::Disabled)
}
fn enabled(&self) -> bool {
matches!(self, Setting::Enabled)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ActiveCodePage {
System,
Utf8,
Legacy,
Locale(String),
}
impl ActiveCodePage {
pub fn as_str(&self) -> &str {
match self {
Self::System => "",
Self::Utf8 => "UTF-8",
Self::Legacy => "Legacy",
Self::Locale(s) => s,
}
}
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
match self {
Self::System => Ok(()),
_ => w.element("activeCodePage", &[WS2019], |w| w.text(self.as_str())),
}
}
}
impl Display for ActiveCodePage {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DpiAwareness {
UnawareByDefault,
Unaware,
System,
PerMonitor,
PerMonitorV2,
PerMonitorV2Only,
}
impl DpiAwareness {
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
let settings = match self {
Self::UnawareByDefault => (None, None),
Self::Unaware => (Some("false"), None),
Self::System => (Some("true"), None),
Self::PerMonitor => (Some("true/pm"), None),
Self::PerMonitorV2 => (Some("true/pm"), Some("permonitorv2,permonitor")),
Self::PerMonitorV2Only => (None, Some("permonitorv2")),
};
if let Some(dpi_aware) = settings.0 {
w.element("dpiAware", &[WS2005], |w| w.text(dpi_aware))?;
}
if let Some(dpi_awareness) = settings.1 {
w.element("dpiAwareness", &[WS2016], |w| w.text(dpi_awareness))?;
}
Ok(())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum HeapType {
LowFragmentationHeap,
SegmentHeap,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ScrollingAwareness {
LowResolution,
HighResolution,
UltraHighResolution,
}
impl ScrollingAwareness {
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
match self {
Self::LowResolution => w.element("ultraHighResolutionScrollingAware", &[WS2013], |w| w.text("false")),
Self::HighResolution => w.element("highResolutionScrollingAware", &[WS2013], |w| w.text("true")),
Self::UltraHighResolution => Ok(()),
}
}
}
#[derive(Debug)]
struct RequestedExecutionLevel {
level: ExecutionLevel,
ui_access: bool,
}
impl RequestedExecutionLevel {
fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
w.element("asmv3:trustInfo", &[], |w| {
w.element("asmv3:security", &[], |w| {
w.element("asmv3:requestedPrivileges", &[], |w| {
w.empty_element(
"asmv3:requestedExecutionLevel",
&[
("level", self.level.as_str()),
("uiAccess", if self.ui_access { "true" } else { "false" }),
],
)
})
})
})
}
}
#[derive(Debug)]
pub enum ExecutionLevel {
AsInvoker,
HighestAvailable,
RequireAdministrator,
}
impl ExecutionLevel {
pub fn as_str(&self) -> &'static str {
match self {
Self::AsInvoker => "asInvoker",
Self::HighestAvailable => "highestAvailable",
Self::RequireAdministrator => "requireAdministrator",
}
}
}
impl Display for ExecutionLevel {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}