rpkg_rs/resource/
runtime_resource_id.rs

1//! Runtime identifier for a Glacier Resource.
2//! Can be derived from a [ResourceID] md5 digest
3
4use crate::misc::resource_id::ResourceID;
5use binrw::binrw;
6use md5::{Digest, Md5};
7use std::fmt;
8use std::fmt::{Debug, Formatter};
9use std::hash::Hash;
10use thiserror::Error;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14#[cfg(feature = "serde")]
15use serde_hex::{SerHex, StrictPfx};
16
17#[derive(Error, Debug)]
18pub enum RuntimeResourceIDError {
19    #[error("{} can't represent a valid runtimeResourceID", _0)]
20    InvalidID(u64),
21
22    #[error("Cannot parse {} to a runtimeResourceID", _0)]
23    ParseError(String),
24}
25
26/// Represents a runtime resource identifier.
27#[derive(Default, PartialEq, Eq, Hash, Clone, Copy)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29#[binrw]
30#[brw(little)]
31pub struct RuntimeResourceID {
32    #[cfg_attr(feature = "serde", serde(with = "SerHex::<StrictPfx>"))]
33    id: u64,
34}
35
36impl PartialEq<u64> for RuntimeResourceID {
37    fn eq(&self, other: &u64) -> bool {
38        self.id == *other
39    }
40}
41
42impl From<u64> for RuntimeResourceID {
43    fn from(value: u64) -> Self {
44        let mut rrid = RuntimeResourceID { id: value };
45        if !rrid.is_valid() {
46            rrid = RuntimeResourceID::invalid();
47        }
48        rrid
49    }
50}
51
52impl From<RuntimeResourceID> for u64 {
53    fn from(value: RuntimeResourceID) -> Self {
54        value.id
55    }
56}
57
58impl From<ResourceID> for RuntimeResourceID {
59    fn from(value: ResourceID) -> Self {
60        Self::from_resource_id(&value)
61    }
62}
63
64impl From<&str> for RuntimeResourceID {
65    fn from(_: &str) -> Self {
66        unimplemented!("Implicit conversion from &str to RuntimeResourceID is not allowed, use the from_raw_string function, or convert from a ResourceID.");
67    }
68}
69
70impl RuntimeResourceID {
71    pub fn to_hex_string(&self) -> String {
72        format!("{:016X}", self.id)
73    }
74    pub fn is_valid(&self) -> bool {
75        self.id < 0x00FFFFFFFFFFFFFF
76    }
77    pub fn invalid() -> Self {
78        Self {
79            id: 0x00FFFFFFFFFFFFFF,
80        }
81    }
82
83    /// Create RuntimeResourceID from ResourceID
84    pub fn from_resource_id(rid: &ResourceID) -> Self {
85        let digest = Md5::digest(rid.resource_path());
86        let mut hash = 0u64;
87        for i in 1..8 {
88            hash |= u64::from(digest[i]) << (8 * (7 - i));
89        }
90
91        Self { id: hash }
92    }
93
94    ///prefer [from_resource_id] when possible
95    pub fn from_raw_string(string: &str) -> Self {
96        let digest = Md5::digest(string);
97        let mut hash = 0u64;
98        for i in 1..8 {
99            hash |= u64::from(digest[i]) << (8 * (7 - i));
100        }
101
102        Self { id: hash }
103    }
104
105    /// Create RuntimeResourceID from hexadecimal string
106    /// Also accepts 0x prefixed strings
107    pub fn from_hex_string(hex_string: &str) -> Result<Self, RuntimeResourceIDError> {
108        let hex_string = if let Some(hex_string) = hex_string.strip_prefix("0x") {
109            hex_string
110        } else {
111            hex_string
112        };
113
114        match u64::from_str_radix(hex_string, 16) {
115            Ok(num) => {
116                let rrid = RuntimeResourceID { id: num };
117                if !rrid.is_valid() {
118                    Err(RuntimeResourceIDError::InvalidID(num))
119                } else {
120                    Ok(rrid)
121                }
122            }
123            Err(_) => Err(RuntimeResourceIDError::ParseError(hex_string.to_string())),
124        }
125    }
126}
127
128impl Debug for RuntimeResourceID {
129    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", self.to_hex_string())
131    }
132}
133
134impl fmt::Display for RuntimeResourceID {
135    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
136        write!(f, "{}", self.to_hex_string())
137    }
138}
139
140// Test section
141#[cfg(test)]
142mod tests {
143    use std::str::FromStr;
144    // Import the test module
145    use super::*;
146
147    #[test]
148    fn test_rrid_conversions() {
149        assert_eq!(
150            RuntimeResourceID::from(0x00123456789ABCDE),
151            0x00123456789ABCDE
152        );
153        assert_eq!(RuntimeResourceID::invalid(), 0x00FFFFFFFFFFFFFF);
154        assert_eq!(
155            RuntimeResourceID::from_raw_string("hello world"),
156            0x00B63BBBE01EEED0
157        );
158        assert_eq!(
159            RuntimeResourceID::from_hex_string("0x00123456789ABCDE").unwrap(),
160            0x00123456789ABCDE
161        );
162        assert_eq!(
163            RuntimeResourceID::from_hex_string("00123456789ABCDE").unwrap(),
164            0x00123456789ABCDE
165        );
166
167        let rid = ResourceID::from_str("[assembly:/_test/lib.a?/test_image.png].pc_webp").unwrap();
168        assert_eq!(
169            RuntimeResourceID::from_resource_id(&rid),
170            0x00290D5B143172A3
171        );
172        assert_eq!(RuntimeResourceID::from(rid), 0x00290D5B143172A3);
173    }
174
175    // Add more test functions as needed
176}