use crate::utils::expand_environment_variables;
use ini::inistr;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use tracing::warn;
use windows::Win32::Foundation::FreeLibrary;
use windows::Win32::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_AS_DATAFILE};
use windows::Win32::UI::WindowsAndMessaging::LoadStringW;
use windows_core::{PCWSTR, PWSTR};
use zerolaunch_plugin_api::services::shell::ResourceLoader;
pub struct WindowsResourceLoader;
impl Default for WindowsResourceLoader {
fn default() -> Self {
Self::new()
}
}
impl WindowsResourceLoader {
pub fn new() -> Self {
Self
}
}
impl ResourceLoader for WindowsResourceLoader {
fn parse_localized_names_from_dir(&self, dir_path: &Path) -> HashMap<String, String> {
parse_localized_names_from_dir(dir_path)
}
}
fn resolve_resource_string(resource_ref: &str) -> Option<String> {
if !resource_ref.starts_with('@') {
return None;
}
let s = &resource_ref[1..];
let comma_pos = s.rfind(',')?;
let (path_part, id_part_with_comma) = s.split_at(comma_pos);
let expanded_path = match expand_environment_variables(path_part) {
Some(path) => path,
None => {
return None;
}
};
let resource_id = match id_part_with_comma[1..].parse::<i32>() {
Ok(id) => id.unsigned_abs(),
Err(e) => {
warn!("Failed to parse resource ID: {}", e);
return None;
}
};
unsafe {
let wide_path: Vec<u16> = OsStr::new(&expanded_path)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let lib_handle = match LoadLibraryExW(
PCWSTR::from_raw(wide_path.as_ptr()),
None,
LOAD_LIBRARY_AS_DATAFILE,
) {
Ok(handle) => handle,
Err(e) => {
warn!("Failed to load library {}: {:?}", expanded_path, e);
return None;
}
};
let mut buffer: [u16; 512] = [0; 512];
let length = LoadStringW(
Some(lib_handle.into()),
resource_id,
PWSTR::from_raw(buffer.as_mut_ptr()),
buffer.len() as i32,
);
let _ = FreeLibrary(lib_handle);
if length > 0 {
return Some(String::from_utf16_lossy(&buffer[..length as usize]));
}
}
None
}
fn parse_localized_names_from_dir(dir_path: &Path) -> HashMap<String, String> {
let ini_path = dir_path.join("desktop.ini");
if !ini_path.exists() {
return HashMap::new();
}
let content = match std::fs::read(&ini_path) {
Ok(bytes) => {
if bytes.starts_with(&[0xFF, 0xFE]) && bytes.len() >= 2 {
let u16_bytes = &bytes[2..];
if u16_bytes.len() % 2 != 0 {
return HashMap::new();
}
let utf16_values: Vec<u16> = u16_bytes
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes(*chunk))
.collect();
widestring::U16Str::from_slice(&utf16_values).to_string_lossy()
} else {
String::from_utf8_lossy(&bytes).to_string()
}
}
Err(e) => {
warn!("Failed to read desktop.ini file: {}", e);
return HashMap::new();
}
};
let conf = inistr!(&content);
let mut localized_map = HashMap::new();
if let Some(section) = conf.get("localizedfilenames") {
for (key, value) in section.iter() {
if value.is_none() {
continue;
}
let value = value
.clone()
.expect("Value should not be None after is_none check");
if value.starts_with('@') {
if let Some(resolved_name) = resolve_resource_string(&value) {
localized_map.insert(key.to_string(), resolved_name);
}
} else {
localized_map.insert(key.to_string(), value.to_string());
}
}
}
localized_map
}