use crate::Off;
use crate::abi::ResourceKey;
use super::ResourceType;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, crate::Shape)]
#[shape(api = "guid")]
pub struct Guid(pub [u8; 16]);
impl Guid {
#[must_use]
pub const fn parse(text: &str) -> Self {
let text = text.as_bytes();
assert!(text.len() == 36, "a GUID has 36 characters");
let mut raw = [0u8; 16];
let (mut i, mut out) = (0, 0);
while i < 36 {
if i == 8 || i == 13 || i == 18 || i == 23 {
assert!(text[i] == b'-', "a GUID has dashes at 8, 13, 18 and 23");
i += 1;
continue;
}
raw[out] = (digit(text[i]) << 4) | digit(text[i + 1]);
out += 1;
i += 2;
}
let bytes = [
raw[3], raw[2], raw[1], raw[0], raw[5], raw[4], raw[7], raw[6], raw[8], raw[9],
raw[10], raw[11], raw[12], raw[13], raw[14], raw[15],
];
assert!(out == 16, "a GUID has 32 hex digits");
Self(bytes)
}
#[must_use]
pub fn try_parse(text: &str) -> Option<Self> {
let bytes = text.as_bytes();
let shaped = bytes.len() == 36
&& bytes.iter().enumerate().all(|(i, &b)| {
if matches!(i, 8 | 13 | 18 | 23) {
b == b'-'
} else {
b.is_ascii_hexdigit()
}
});
shaped.then(|| Self::parse(text))
}
#[must_use]
pub const fn fnv1a(self) -> u32 {
let mut hash: u32 = 0x811c_9dc5;
let mut i = 0;
while i < 16 {
hash ^= self.0[i] as u32;
hash = hash.wrapping_mul(0x0100_0193);
i += 1;
}
hash
}
pub fn key(self, ty: ResourceType) -> Result<ResourceKey, Off> {
Ok(self.key_by_hash(ty.hash()?))
}
#[must_use]
pub const fn key_by_hash(self, type_hash: u32) -> ResourceKey {
ResourceKey {
guid: self.0,
type_hash,
part: 0,
}
}
#[must_use]
pub const fn named(id: &str) -> Self {
assert!(is_id(id), "a content id is <mod>:<name>");
Self::hashed(id)
}
#[must_use]
pub const fn try_named(id: &str) -> Option<Self> {
if is_id(id) {
Some(Self::hashed(id))
} else {
None
}
}
pub(crate) const fn hashed(name: &str) -> Self {
let input = const_sha1::ConstSlice::new()
.push_slice(&NAMESPACE_BYTES)
.push_slice(name.as_bytes());
let hash = const_sha1::sha1_from_const_slice(&input).as_bytes();
let mut raw = [0u8; 16];
let mut i = 0;
while i < 16 {
raw[i] = hash[i];
i += 1;
}
raw[6] = (raw[6] & 0x0f) | 0x50;
raw[8] = (raw[8] & 0x3f) | 0x80;
Self::from_big_endian(raw)
}
const fn from_big_endian(raw: [u8; 16]) -> Self {
Self([
raw[3], raw[2], raw[1], raw[0], raw[5], raw[4], raw[7], raw[6], raw[8], raw[9],
raw[10], raw[11], raw[12], raw[13], raw[14], raw[15],
])
}
}
pub const NAMESPACE: Guid = Guid::parse("e2185889-0e5e-4977-9edd-d6e7bcd1393f");
pub(super) const NAMESPACE_BYTES: [u8; 16] = [
0xe2, 0x18, 0x58, 0x89, 0x0e, 0x5e, 0x49, 0x77, 0x9e, 0xdd, 0xd6, 0xe7, 0xbc, 0xd1, 0x39, 0x3f,
];
#[must_use]
pub const fn is_id(id: &str) -> bool {
let bytes = id.as_bytes();
let mut colon = None;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b':' {
if colon.is_some() {
return false;
}
colon = Some(i);
} else {
let common = b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-';
let in_name = colon.is_some() && b == b'/';
if !common && !in_name && b != b'.' {
return false;
}
}
i += 1;
}
match colon {
Some(at) => {
let name = bytes.len() - at - 1;
at >= 1 && at <= 64 && name >= 1 && name <= 128
}
None => false,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Id {
pub(super) name: &'static str,
pub(super) guid: Guid,
}
impl Id {
#[must_use]
pub const fn new(name: &'static str) -> Self {
Self {
name,
guid: Guid::named(name),
}
}
#[must_use]
pub const fn try_new(name: &'static str) -> Option<Self> {
match Guid::try_named(name) {
Some(guid) => Some(Self { name, guid }),
None => None,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
self.name
}
#[must_use]
pub fn namespace(self) -> &'static str {
self.name
.split_once(':')
.map_or("", |(namespace, _)| namespace)
}
#[must_use]
pub const fn guid(self) -> Guid {
self.guid
}
pub fn key(self, ty: ResourceType) -> Result<ResourceKey, Off> {
self.guid.key(ty)
}
#[must_use]
pub const fn key_by_hash(self, type_hash: u32) -> ResourceKey {
self.guid.key_by_hash(type_hash)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DebugName<'a>(pub &'a str);
impl From<Id> for Guid {
fn from(id: Id) -> Self {
id.guid
}
}
impl core::fmt::Display for Id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name)
}
}
impl core::fmt::Display for Guid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let b = &self.0;
write!(
f,
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-",
b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6], b[8], b[9]
)?;
b[10..].iter().try_for_each(|byte| write!(f, "{byte:02x}"))
}
}
#[expect(
clippy::panic,
reason = "const evaluation: a malformed literal fails the build"
)]
pub(super) const fn digit(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => panic!("a GUID is hex"),
}
}
#[must_use]
pub fn game_resource(key: ResourceKey) -> Option<Vec<u8>> {
crate::host()?.game_resource(key)
}