rpkg_rs/resource/
runtime_resource_id.rs1use 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
17const RRID_ID_MASK: u64 = 0x00FF_FFFF_FFFF_FFFF;
18
19#[derive(Error, Debug)]
20pub enum RuntimeResourceIDError {
21 #[error("{} can't represent a valid runtimeResourceID", _0)]
22 InvalidID(u64),
23
24 #[error("Cannot parse {} to a runtimeResourceID", _0)]
25 ParseError(String),
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29#[repr(u8)]
30pub enum PlatformTag {
31 None = 0x00,
32 Pc = 0x01,
33 Ps5 = 0x02,
34 Scarlett = 0x03, Ounce = 0x04, }
37
38impl PlatformTag {
39 const fn from_u8(bit: u8) -> Option<PlatformTag> {
40 match bit {
41 0x00 => Some(PlatformTag::None),
42 0x01 => Some(PlatformTag::Pc),
43 0x02 => Some(PlatformTag::Ps5),
44 0x03 => Some(PlatformTag::Scarlett),
45 0x04 => Some(PlatformTag::Ounce),
46 _ => None,
47 }
48 }
49}
50
51#[derive(Default, PartialEq, Eq, Hash, Clone, Copy)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54#[binrw]
55#[brw(little)]
56pub struct RuntimeResourceID {
57 #[cfg_attr(feature = "serde", serde(with = "SerHex::<StrictPfx>"))]
58 id: u64,
59}
60
61impl PartialEq<u64> for RuntimeResourceID {
62 fn eq(&self, other: &u64) -> bool {
63 self.id == *other
64 }
65}
66
67impl From<u64> for RuntimeResourceID {
68 fn from(value: u64) -> Self {
69 let mut rrid = RuntimeResourceID { id: value };
70 if !rrid.is_valid() {
71 rrid = RuntimeResourceID::invalid();
72 }
73 rrid
74 }
75}
76
77impl From<RuntimeResourceID> for u64 {
78 fn from(value: RuntimeResourceID) -> Self {
79 value.id
80 }
81}
82
83#[allow(deprecated)]
84impl From<ResourceID> for RuntimeResourceID {
85 fn from(value: ResourceID) -> Self {
86 Self::from_resource_id(&value)
87 }
88}
89
90impl From<&str> for RuntimeResourceID {
91 fn from(_: &str) -> Self {
92 unimplemented!("Implicit conversion from &str to RuntimeResourceID is not allowed, use the from_raw_string function, or convert from a ResourceID.");
93 }
94}
95
96impl RuntimeResourceID {
97 pub fn to_hex_string(&self) -> String {
98 format!("{:016X}", self.id)
99 }
100 pub fn is_valid(&self) -> bool {
101 self.platform().is_some() && self.id & RRID_ID_MASK != RRID_ID_MASK
102 }
103 pub fn invalid() -> Self {
104 Self {
105 id: 0x00FFFFFFFFFFFFFF,
106 }
107 }
108
109 #[deprecated(
111 since = "1.4.0",
112 note = "from_resource_id() hashes the ResourceID using the PC platform and is not platform-agnostic. \
113 Use from_resource_id_with_platform(..., \"pc\", ...) instead. \
114 In a future release `from_resource_id()` will hash the platform-agnostic ResourceID form by default."
115 )]
116 pub fn from_resource_id(rid: &ResourceID) -> Self {
117 Self::from_raw_string(&rid.resource_path_with_platform("pc"))
118 }
119
120 pub fn from_resource_id_with_platform(rid: &ResourceID, resource_platform: &str, runtime_platform: PlatformTag) -> Self {
127 Self::from_raw_string(&rid.resource_path_with_platform(resource_platform)).with_platform(runtime_platform)
128 }
129
130 pub fn from_raw_string(string: &str) -> Self {
132 let digest = Md5::digest(string);
133 let mut hash = 0u64;
134 for i in 1..8 {
135 hash |= u64::from(digest[i]) << (8 * (7 - i));
136 }
137
138 Self { id: hash }
139 }
140
141 pub fn from_hex_string(hex_string: &str) -> Result<Self, RuntimeResourceIDError> {
144 let hex_string = hex_string
145 .strip_prefix("0x")
146 .or_else(|| hex_string.strip_prefix("0X"))
147 .unwrap_or(hex_string);
148
149 match u64::from_str_radix(hex_string, 16) {
150 Ok(num) => {
151 let rrid = RuntimeResourceID { id: num };
152 if !rrid.is_valid() {
153 Err(RuntimeResourceIDError::InvalidID(num))
154 } else {
155 Ok(rrid)
156 }
157 }
158 Err(_) => Err(RuntimeResourceIDError::ParseError(hex_string.to_string())),
159 }
160 }
161
162 pub const fn platform(self) -> Option<PlatformTag> {
163 PlatformTag::from_u8((self.id >> 56) as u8)
164 }
165
166 pub const fn with_platform(self, platform: PlatformTag) -> Self {
167 Self {
168 id: ((platform as u64) << 56) | (self.id & RRID_ID_MASK),
169 }
170 }
171
172 pub fn from_raw_string_with_platform(string: &str, platform: PlatformTag) -> Self {
173 Self::from_raw_string(string).with_platform(platform)
174 }
175}
176
177impl Debug for RuntimeResourceID {
178 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
179 write!(f, "{}", self.to_hex_string())
180 }
181}
182
183impl fmt::Display for RuntimeResourceID {
184 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
185 write!(f, "{}", self.to_hex_string())
186 }
187}
188
189#[cfg(test)]
191mod tests {
192 use std::str::FromStr;
193 use super::*;
195
196 #[test]
197 fn test_rrid_conversions() {
198 assert_eq!(
199 RuntimeResourceID::from(0x00123456789ABCDE),
200 0x00123456789ABCDE
201 );
202 assert_eq!(RuntimeResourceID::invalid(), 0x00FFFFFFFFFFFFFF);
203 assert_eq!(
204 RuntimeResourceID::from_raw_string("hello world"),
205 0x00B63BBBE01EEED0
206 );
207 assert_eq!(
208 RuntimeResourceID::from_hex_string("0x00123456789ABCDE").unwrap(),
209 0x00123456789ABCDE
210 );
211 assert_eq!(
212 RuntimeResourceID::from_hex_string("00123456789ABCDE").unwrap(),
213 0x00123456789ABCDE
214 );
215
216 let rid = ResourceID::from_str("[assembly:/_test/lib.a?/test_image.png].pc_webp").unwrap();
217 assert_eq!(
218 RuntimeResourceID::from_resource_id_with_platform(&rid, "pc", PlatformTag::None),
219 0x00290D5B143172A3
220 );
221 assert_eq!(RuntimeResourceID::from(rid), 0x00290D5B143172A3);
222 }
223
224 #[test]
225 fn platform_extraction_works() {
226 let rrid = RuntimeResourceID::from_raw_string("hello world").with_platform(PlatformTag::Ps5);
227 assert_eq!(rrid, 0x02B63BBBE01EEED0);
228 assert_eq!(rrid.platform(), Some(PlatformTag::Ps5));
229 }
230
231 #[test]
232 fn plain_rrid_has_none_platform() {
233 let rrid = RuntimeResourceID::from_raw_string("hello world");
234 assert_eq!(rrid.platform(), Some(PlatformTag::None));
235 assert!(rrid.is_valid());
236 }
237
238 #[test]
239 fn invalid_rrid_is_invalid() {
240 let rrid = RuntimeResourceID { id: 0x00FFFFFFFFFFFFFF };
241 assert!(!rrid.is_valid());
242 }
243
244 #[test]
245 fn unknown_platform_is_invalid() {
246 let rrid = RuntimeResourceID { id: 0x99B63BBBE01EEED0 };
247 assert!(!rrid.is_valid());
248 }
249
250 #[test]
251 fn with_platform_preserves_id() {
252 let rrid = RuntimeResourceID::from_raw_string("hello world");
253 let tagged = rrid.with_platform(PlatformTag::Ounce);
254 assert_eq!(tagged, 0x04B63BBBE01EEED0);
255 }
256}