#![allow(unexpected_cfgs)]
#![warn(missing_docs)]
use binrw::BinReaderExt;
use getset::{Getters, MutGetters};
#[allow(unused)]
use log::{debug, error, info, trace, warn};
#[cfg(feature = "serde")]
use serde::Serialize;
use std::io::BufReader;
#[cfg(feature = "binwrite")]
use std::io::BufWriter;
use std::path::Path;
use std::{fs::File, io::Seek};
mod header;
pub use header::{
FileAttributeFlags, HotkeyFlags, HotkeyKey, HotkeyModifiers, LinkFlags, ShellLinkHeader,
ShowCommand,
};
pub mod linktarget;
pub use linktarget::LinkTargetIdList;
pub mod linkinfo;
pub use linkinfo::LinkInfo;
mod stringdata;
pub use stringdata::StringData;
pub mod extradata;
pub use extradata::ExtraData;
mod generic_types;
pub use generic_types::filetime::FileTime;
pub use generic_types::guid::*;
pub use generic_types::idlist::*;
mod current_offset;
pub use current_offset::*;
mod strings;
pub use strings::*;
mod itemid;
pub use itemid::*;
#[macro_use]
mod binread_flags;
mod error;
pub use error::Error;
#[derive(Debug, Getters, MutGetters)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[getset(get = "pub", get_mut = "pub")]
pub struct ShellLink {
header: header::ShellLinkHeader,
#[cfg_attr(feature = "serde", serde(skip))]
linktarget_id_list: Option<linktarget::LinkTargetIdList>,
link_info: Option<linkinfo::LinkInfo>,
string_data: StringData,
#[allow(unused)]
extra_data: extradata::ExtraData,
#[serde(skip)]
#[getset(skip)]
encoding: &'static encoding_rs::Encoding,
}
impl Default for ShellLink {
fn default() -> Self {
let header = header::ShellLinkHeader::default();
let encoding = if header.link_flags().contains(LinkFlags::IS_UNICODE) {
encoding_rs::UTF_16LE
} else {
encoding_rs::WINDOWS_1252
};
Self {
header,
linktarget_id_list: None,
link_info: None,
string_data: Default::default(),
extra_data: Default::default(),
encoding,
}
}
}
impl ShellLink {
pub fn new_simple<P: AsRef<Path>>(to: P) -> std::io::Result<Self> {
use std::fs;
use std::path::PathBuf;
let meta = fs::metadata(&to)?;
let mut canonical = fs::canonicalize(&to)?.into_boxed_path();
if cfg!(windows) {
let can_os = canonical.as_os_str().to_str().unwrap();
if let Some(stripped) = can_os.strip_prefix("\\\\?\\") {
canonical = PathBuf::new().join(stripped).into_boxed_path();
}
}
let mut sl = Self::default();
if meta.is_dir() {
sl.header_mut()
.set_file_attributes(FileAttributeFlags::FILE_ATTRIBUTE_DIRECTORY);
} else {
sl.set_relative_path(Some(format!(
".\\{}",
canonical.file_name().unwrap().to_str().unwrap()
)));
sl.set_working_dir(Some(
canonical.parent().unwrap().to_str().unwrap().to_string(),
));
}
Ok(sl)
}
pub fn with_encoding(mut self, encoding: &StringEncoding) -> Self {
match encoding {
StringEncoding::Unicode => {
self.header
.link_flags_mut()
.set(LinkFlags::IS_UNICODE, true);
self.encoding = encoding_rs::UTF_16LE;
}
StringEncoding::CodePage(cp) => {
self.header
.link_flags_mut()
.set(LinkFlags::IS_UNICODE, false);
self.encoding = cp;
}
}
self
}
#[cfg(feature = "binwrite")]
#[cfg_attr(feature = "binwrite", stability::unstable(feature = "save"))]
pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), Error> {
use binrw::BinWrite;
let mut w = BufWriter::new(File::create(path)?);
debug!("Writing header...");
self.header()
.write_le(&mut w)
.map_err(|be| Error::while_writing("Header", be))?;
let link_flags = *self.header().link_flags();
debug!("Writing StringData...");
self.string_data
.write_le_args(&mut w, (link_flags, self.encoding))
.map_err(|be| Error::while_writing("StringData", be))?;
Ok(())
}
pub fn open<P: AsRef<std::path::Path>>(
path: P,
encoding: crate::strings::Encoding,
) -> Result<Self, Error> {
debug!("Opening {:?}", path.as_ref());
let mut reader = BufReader::new(File::open(path)?);
trace!("Reading file.");
let shell_link_header: ShellLinkHeader = reader
.read_le()
.map_err(|be| Error::while_parsing("ShellLinkHeader", be))?;
debug!("Shell header: {:#?}", shell_link_header);
let mut linktarget_id_list = None;
let link_flags = *shell_link_header.link_flags();
if link_flags.contains(LinkFlags::HAS_LINK_TARGET_ID_LIST) {
debug!(
"A LinkTargetIDList is marked as present. Parsing now at position 0x{:0x}",
reader.stream_position()?
);
let list: LinkTargetIdList = reader
.read_le()
.map_err(|be| Error::while_parsing("LinkTargetIdList", be))?;
debug!("LinkTargetIDList: {:#?}", list);
linktarget_id_list = Some(list);
}
let mut link_info = None;
if link_flags.contains(LinkFlags::HAS_LINK_INFO) {
let link_info_offset = reader.stream_position().unwrap();
debug!(
"LinkInfo is marked as present. Parsing now at position 0x{:0x}",
reader.stream_position().unwrap()
);
let info: LinkInfo = reader
.read_le_args((encoding,))
.map_err(|be| Error::while_parsing("LinkInfo", be))?;
debug!("{:#?}", info);
debug_assert_eq!(
reader.stream_position().unwrap(),
link_info_offset + u64::from(*(info.link_info_size()))
);
link_info = Some(info);
}
debug!(
"reading StringData at 0x{:08x}",
reader.stream_position().unwrap()
);
let string_data: StringData = reader
.read_le_args((link_flags, encoding))
.map_err(|be| Error::while_parsing("StringData", be))?;
debug!("{:#?}", string_data);
debug!(
"reading ExtraData at 0x{:08x}",
reader.stream_position().unwrap()
);
let extra_data: ExtraData = reader
.read_le_args((encoding,))
.map_err(|be| Error::while_parsing("ExtraData", be))?;
let encoding = if shell_link_header
.link_flags()
.contains(LinkFlags::IS_UNICODE)
{
encoding_rs::UTF_16LE
} else {
encoding
};
Ok(Self {
header: shell_link_header,
linktarget_id_list,
link_info,
string_data,
extra_data,
encoding,
})
}
pub fn link_target(&self) -> Option<String> {
if let Some(info) = self.link_info().as_ref() {
let mut base_path = if info
.link_info_flags()
.has_common_network_relative_link_and_path_suffix()
{
info.common_network_relative_link()
.as_ref()
.expect("missing common network relative link")
.name()
} else {
info.local_base_path_unicode()
.as_ref()
.map(|s| &s[..])
.or(info.local_base_path())
.expect("missing local base path")
.to_string()
};
let common_path = info
.common_path_suffix_unicode()
.as_ref()
.map(|s| &s[..])
.unwrap_or(info.common_path_suffix());
if !common_path.is_empty() {
if !base_path.ends_with('\\') {
base_path.push('\\');
}
base_path.push_str(common_path);
}
Some(base_path)
} else {
None
}
}
pub fn set_name(&mut self, name: Option<String>) {
self.header_mut()
.update_link_flags(LinkFlags::HAS_NAME, name.is_some());
self.string_data_mut().set_name_string(name);
}
pub fn set_relative_path(&mut self, relative_path: Option<String>) {
self.header_mut()
.update_link_flags(LinkFlags::HAS_RELATIVE_PATH, relative_path.is_some());
self.string_data_mut().set_relative_path(relative_path);
}
pub fn set_working_dir(&mut self, working_dir: Option<String>) {
self.header_mut()
.update_link_flags(LinkFlags::HAS_WORKING_DIR, working_dir.is_some());
self.string_data_mut().set_working_dir(working_dir);
}
pub fn set_arguments(&mut self, arguments: Option<String>) {
self.header_mut()
.update_link_flags(LinkFlags::HAS_ARGUMENTS, arguments.is_some());
self.string_data_mut().set_command_line_arguments(arguments);
}
pub fn set_icon_location(&mut self, icon_location: Option<String>) {
self.header_mut()
.update_link_flags(LinkFlags::HAS_ICON_LOCATION, icon_location.is_some());
self.string_data_mut().set_icon_location(icon_location);
}
}