winres_edit/
resources.rs

1use crate::id::*;
2use crate::result::*;
3use crate::utils::*;
4use crate::version::*;
5use std::path::Path;
6use std::{
7    fmt,
8    path::PathBuf,
9    sync::{Arc, Mutex},
10};
11use windows::{
12    core::PCSTR,
13    Win32::Foundation::{BOOL, HANDLE, HINSTANCE},
14    Win32::System::LibraryLoader::*,
15};
16
17pub mod resource_type {
18    //!
19    //! List of resource constants representing Windows resource types
20    //! expressed as [`Id`]
21    //!
22    use super::Id;
23    pub const UNKNOWN: Id = Id::Integer(0);
24    pub const ACCELERATOR: Id = Id::Integer(9);
25    pub const ANICURSOR: Id = Id::Integer(21);
26    pub const ANIICON: Id = Id::Integer(22);
27    pub const BITMAP: Id = Id::Integer(2);
28    pub const CURSOR: Id = Id::Integer(1);
29    pub const DIALOG: Id = Id::Integer(5);
30    pub const DLGINCLUDE: Id = Id::Integer(17);
31    pub const FONT: Id = Id::Integer(8);
32    pub const FONTDIR: Id = Id::Integer(7);
33    pub const HTML: Id = Id::Integer(23);
34    pub const ICON: Id = Id::Integer(3);
35    pub const MANIFEST: Id = Id::Integer(24);
36    pub const MENU: Id = Id::Integer(4);
37    pub const MESSAGETABLE: Id = Id::Integer(11);
38    pub const PLUGPLAY: Id = Id::Integer(19);
39    pub const VERSION: Id = Id::Integer(16);
40    pub const VXD: Id = Id::Integer(20);
41}
42
43/// Placeholder for future data serialization (not implementated)
44#[derive(Debug, Clone)]
45pub struct ResourceDataInner {
46    // ...
47}
48
49/// Placeholder for future data serialization (not implementated)
50#[derive(Debug, Clone)]
51pub enum ResourceData {
52    Accelerator(ResourceDataInner),
53    AniCursor(ResourceDataInner),
54    AniIcon(ResourceDataInner),
55    Bitmap(ResourceDataInner),
56    Cursor(ResourceDataInner),
57    Dialog(ResourceDataInner),
58    DialogInclude(ResourceDataInner),
59    Font(ResourceDataInner),
60    FontDirectory(ResourceDataInner),
61    Html(ResourceDataInner),
62    Icon(ResourceDataInner),
63    Manifest(ResourceDataInner),
64    Menu(ResourceDataInner),
65    MessageTable(ResourceDataInner),
66    PlugPlay(ResourceDataInner),
67    Version(VersionInfo),
68    VxD(ResourceDataInner),
69    Unknown(ResourceDataInner),
70}
71
72/// Structure representing a single resource
73#[derive(Clone)]
74pub struct Resource {
75    /// resource type
76    pub kind: Id,
77    /// resource name
78    pub name: Id,
79    /// `u16` language associated with the resource
80    pub lang: u16,
81    /// raw resource data
82    pub encoded: Arc<Mutex<Vec<u8>>>,
83    /// destructured resource data (not implemented)
84    pub decoded: Arc<Mutex<Option<ResourceData>>>,
85    /// reference to the module handle that owns the resource
86    module_handle: Arc<Mutex<Option<HANDLE>>>,
87}
88
89impl std::fmt::Debug for Resource {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("")
92            .field("kind", &self.kind)
93            .field("name", &self.name)
94            .field("lang", &self.lang)
95            .field("data len", &self.encoded.lock().unwrap().len())
96            // .field("resource",&self.decoded)
97            //  .field("resource",&format!("{:?}",self.resource))
98            .finish()
99    }
100}
101
102impl Resource {
103    /// Create a new resource instance bound to the [`Resources`] resource manager.
104    pub fn new(
105        resources: &Resources,
106        rtype: PCSTR,
107        rname: PCSTR,
108        rlang: u16,
109        data: &[u8],
110    ) -> Resource {
111        let typeid: Id = rtype.into();
112        Resource {
113            kind: typeid,
114            name: rname.into(),
115            lang: rlang,
116            encoded: Arc::new(Mutex::new(data.to_vec())),
117            decoded: Arc::new(Mutex::new(None)),
118            module_handle: resources.module_handle(),
119        }
120    }
121
122    /// Remove resource from the associated module (deletes the resource)
123    pub fn remove(&self) -> Result<&Self> {
124        if let Some(handle) = self.module_handle.lock().unwrap().as_ref() {
125            let success = unsafe {
126                UpdateResourceA(
127                    *handle,
128                    self.kind.clone(),
129                    self.name.clone(),
130                    self.lang,
131                    None,
132                    0,
133                )
134                .as_bool()
135            };
136
137            if !success {
138                return Err(format!(
139                    "Resources::load(): Error removing resources: {:?}",
140                    get_last_error()
141                )
142                .into());
143            }
144        } else {
145            return Err("Resource::replace(): resource file is not open".into());
146        };
147
148        Ok(self)
149    }
150
151    /// Replace raw resource data with a user-supplied data. This only replaces
152    /// the data in the resource structure. You must call [`Resource::update()`]
153    /// following this call to update the resoruce data in the actual module.
154    pub fn replace(&self, data: &[u8]) -> Result<&Self> {
155        *self.encoded.lock().unwrap() = data.to_vec();
156        Ok(self)
157    }
158
159    /// Store this resource in the resource module (creates new or updates)
160    pub fn update(&self) -> Result<&Self> {
161        if let Some(handle) = self.module_handle.lock().unwrap().as_ref() {
162            let encoded = self.encoded.lock().unwrap();
163            let success = unsafe {
164                UpdateResourceA(
165                    *handle,
166                    self.kind.clone(),
167                    self.name.clone(),
168                    self.lang,
169                    Some(std::mem::transmute(encoded.as_ptr())),
170                    encoded.len() as u32,
171                )
172                .as_bool()
173            };
174
175            if !success {
176                return Err(format!(
177                    "Resources::load(): Error removing resources: {:?}",
178                    get_last_error()
179                )
180                .into());
181            }
182        } else {
183            return Err("Resource::replace(): resource file is not open".into());
184        };
185
186        Ok(self)
187    }
188}
189
190/// Data structure representing a resource file. This data structure
191/// points to a `.res` or `.exe` file and allows loading and modifying
192/// resource in this file.
193#[derive(Debug)]
194pub struct Resources {
195    file: PathBuf,
196    module_handle: Arc<Mutex<Option<HANDLE>>>,
197    /// resources contained in the supplied file represented by the [`Resource`] data structure.
198    pub list: Arc<Mutex<Vec<Arc<Resource>>>>,
199}
200
201impl Resources {
202    /// Create new instance of the resource manager bound to a specific resource file.
203    /// Once created, the resource file should be opened using [`Resources::open()`] or [`Resources::load()`].
204    pub fn new(file: &Path) -> Resources {
205        Resources {
206            file: file.to_path_buf(),
207            module_handle: Arc::new(Mutex::new(None)),
208            list: Arc::new(Mutex::new(Vec::new())),
209        }
210    }
211
212    /// Load resources from the resource file.  This function does not need to be called
213    /// explicitly as [`Resources::open`] will call it. It is useful if you want to load
214    /// resources for extraction purposes only.
215    pub fn load(&self) -> Result<()> {
216        unsafe {
217            let handle = LoadLibraryExA(
218                pcstr!(self.file.to_str().unwrap()),
219                None,
220                // LOAD_LIBRARY_FLAGS::default()
221                DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE,
222            )?;
223
224            let ptr: *const Resources = std::mem::transmute(&*self);
225            let success =
226                EnumResourceTypesA(handle, Some(enum_types), std::mem::transmute(ptr)).as_bool();
227
228            FreeLibrary(handle);
229
230            if !success {
231                return Err(format!(
232                    "Resources::load(): Error enumerating resources: {:?}",
233                    get_last_error()
234                )
235                .into());
236            }
237        }
238
239        Ok(())
240    }
241
242    pub fn module_handle(&self) -> Arc<Mutex<Option<HANDLE>>> {
243        self.module_handle.clone()
244    }
245
246    /// returns `true` if the resource file is currently open
247    pub fn is_open(&self) -> bool {
248        self.module_handle.lock().unwrap().is_some()
249    }
250
251    /// Open the resource file. This function opens a Windows handle to
252    /// the resource file and must be followed by [`Resources::close`].
253    pub fn open(&mut self) -> Result<&Self> {
254        self.open_impl(false)
255    }
256
257    /// Opens the resource file with `delete_existing_resources` set to `true`.
258    /// This will result in retention in a deletion of previously existing resources.
259    pub fn open_delete_existing_resources(&mut self) -> Result<&Self> {
260        self.open_impl(true)
261    }
262
263    fn open_impl(&mut self, delete_existing_resources: bool) -> Result<&Self> {
264        if self.is_open() {
265            return Err(
266                format!("resource '{}' is already open", self.file.to_str().unwrap()).into(),
267            );
268        }
269
270        self.load()?;
271
272        let handle = unsafe {
273            BeginUpdateResourceA(
274                pcstr!(self.file.to_str().unwrap()),
275                delete_existing_resources,
276            )?
277        };
278
279        self.module_handle.lock().unwrap().replace(handle);
280        // self.handle.lock().unwrap().replace(handle);
281
282        Ok(self)
283    }
284
285    /// Remove the supplied resource from the resource file.
286    pub fn remove(&self, resource: &Resource) -> Result<&Self> {
287        self.remove_with_args(&resource.kind, &resource.name, resource.lang)?;
288        Ok(self)
289    }
290
291    /// Remove the resource from the resource file by specifying resource type, name and lang.
292    /// WARNING: If this method fails, the entire update set may fail (this is true for any API calls).
293    /// As such it is highly recommended to use [`Resources::remove`] instead and supplying and existing
294    /// [`Resource`] struct as it ensures that all supplied information is correct.
295    /// This method is provided for advanced usage only.
296    pub fn remove_with_args(&self, kind: &Id, name: &Id, lang: u16) -> Result<&Self> {
297        // if let Some(handle) = self.handle.lock().unwrap().as_ref() {
298        if let Some(handle) = self.module_handle.lock().unwrap().as_ref() {
299            let success = unsafe { UpdateResourceA(*handle, kind, name, lang, None, 0).as_bool() };
300
301            if !success {
302                return Err(format!(
303                    "Resources::load(): Error removing resources: {:?}",
304                    get_last_error()
305                )
306                .into());
307            }
308        } else {
309            return Err(format!("resource '{}' is not open", self.file.to_str().unwrap()).into());
310        };
311
312        Ok(self)
313    }
314
315    /// Replace (Update) the resource in the resource file. It is expected that this is the
316    /// original resource with the modified raw data.
317    pub fn try_replace(&self, resource: &Resource) -> Result<&Self> {
318        self.replace_with_args(
319            &resource.kind,
320            &resource.name,
321            resource.lang,
322            &resource.encoded.lock().unwrap(),
323        )?;
324        Ok(self)
325    }
326
327    /// Replace (Update) the resource in the resource file by supplying the resource type, name and lang
328    /// as well as a `u8` slice containing the raw resource data.  Please note that if this function fails
329    /// the entire resoruce update set may fail.
330    pub fn replace_with_args(&self, kind: &Id, name: &Id, lang: u16, data: &[u8]) -> Result<&Self> {
331        if let Some(handle) = self.module_handle.lock().unwrap().as_ref() {
332            let success = unsafe {
333                UpdateResourceA(
334                    *handle,
335                    kind,
336                    name,
337                    lang,
338                    Some(std::mem::transmute(data.as_ptr())),
339                    data.len() as u32,
340                )
341                .as_bool()
342            };
343
344            if !success {
345                return Err(format!(
346                    "Resources::load(): Error updating resources: {:?}",
347                    get_last_error()
348                )
349                .into());
350            }
351        } else {
352            return Err(format!(
353                "resource file '{}' is not open",
354                self.file.to_str().unwrap()
355            )
356            .into());
357        };
358
359        Ok(self)
360    }
361
362    /// Close the resource file.  This applies all the changes (updates) to the resource file.
363    pub fn close(&mut self) {
364        if let Some(handle) = self.module_handle.lock().unwrap().take() {
365            unsafe {
366                EndUpdateResourceA(handle, false);
367            };
368        }
369    }
370
371    /// Close the resource file discarding all changes.
372    pub fn discard(&mut self) {
373        if let Some(handle) = self.module_handle.lock().unwrap().take() {
374            unsafe {
375                EndUpdateResourceA(handle, true);
376            };
377        }
378    }
379
380    /// Create a new resource entry in the resource file. This function
381    /// expects a valid [`Resource`] structure containing an appropriate
382    /// resource type, name and raw data.
383    pub fn insert(&self, r: Resource) {
384        self.list.lock().unwrap().push(Arc::new(r))
385    }
386
387    /// Locate a resource entry by type and name.
388    pub fn find(&self, typeid: Id, nameid: Id) -> Option<Arc<Resource>> {
389        for item in self.list.lock().unwrap().iter() {
390            if item.kind == typeid && item.name == nameid {
391                return Some(item.clone());
392            }
393        }
394
395        None
396    }
397
398    /// Locate and deserialize VS_VERSIONINFO structure (represented by [`VersionInfo`]).
399    pub fn get_version_info(&self) -> Result<Option<VersionInfo>> {
400        for item in self.list.lock().unwrap().iter() {
401            if item.kind == resource_type::VERSION {
402                return Ok(Some(item.clone().try_into()?));
403            }
404        }
405
406        Ok(None)
407    }
408}
409
410impl Drop for Resources {
411    fn drop(&mut self) {
412        self.close();
413    }
414}
415
416unsafe extern "system" fn enum_languages(
417    hmodule: HINSTANCE,
418    lptype: PCSTR,
419    lpname: PCSTR,
420    lang: u16,
421    lparam: isize,
422) -> BOOL {
423    let rptr: *const Resources = std::mem::transmute(lparam);
424    let hresinfo = match FindResourceExA(hmodule, lptype, lpname, lang) {
425        Ok(hresinfo) => hresinfo,
426        Err(e) => panic!("Unable to find resource {hmodule:?} {lptype:?} {lpname:?} {lang}: {e}"),
427    };
428    let resource = LoadResource(hmodule, hresinfo);
429    let len = SizeofResource(hmodule, hresinfo);
430    let data_ptr = LockResource(resource);
431    let data = std::slice::from_raw_parts(std::mem::transmute(data_ptr), len as usize);
432    let resources = &*rptr;
433    resources.insert(Resource::new(resources, lptype, lpname, lang, data));
434    BOOL(1)
435}
436
437unsafe extern "system" fn enum_names(
438    hmodule: HINSTANCE,
439    lptype: PCSTR,
440    lpname: PCSTR,
441    lparam: isize,
442) -> BOOL {
443    EnumResourceLanguagesA(hmodule, lptype, lpname, Some(enum_languages), lparam);
444    BOOL(1)
445}
446
447unsafe extern "system" fn enum_types(hmodule: HINSTANCE, lptype: PCSTR, lparam: isize) -> BOOL {
448    EnumResourceNamesA(hmodule, lptype, Some(enum_names), lparam);
449    BOOL(1)
450}