Skip to main content

steel_core/player/profile/
mod.rs

1//! This module contains the `GameProfile` struct, which is used to store information about a player's profile.
2
3mod known_players;
4mod lookup;
5
6pub(crate) use known_players::{GAME_PROFILE_CACHE_LIMIT, KnownPlayerNameLookup};
7pub use known_players::{KnownPlayer, KnownPlayers};
8pub use lookup::ProfileLookupError;
9pub(crate) use lookup::lookup_online_profile;
10
11use serde::{Deserialize, Serialize};
12use steel_protocol::packets::login::{GameProfileProperty, LoginGameProfile};
13use uuid::{Builder, Uuid, Variant, Version};
14
15/// An enum representing a profile action.
16#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
17#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
18pub enum GameProfileAction {
19    /// The player has been forced to change their name.
20    ForcedNameChange,
21    /// The player is using a banned skin.
22    UsingBannedSkin,
23}
24
25/// A struct representing a player's game profile.
26#[derive(Deserialize, Clone, Debug)]
27pub struct GameProfile {
28    /// The player's UUID.
29    pub id: Uuid,
30    /// The player's name.
31    pub name: String,
32    /// A list of properties for the player's profile.
33    pub properties: Vec<GameProfileProperty>,
34    /// A list of profile actions for the player.
35    #[serde(rename = "profileActions")]
36    pub profile_actions: Option<Vec<GameProfileAction>>,
37}
38
39impl<'a> From<&'a GameProfile> for LoginGameProfile<'a> {
40    fn from(profile: &'a GameProfile) -> Self {
41        LoginGameProfile {
42            id: profile.id,
43            name: &profile.name,
44            properties: &profile.properties,
45        }
46    }
47}
48
49/// Returns whether a name is valid for online login and profile lookup.
50#[must_use]
51pub fn is_valid_player_name(name: &str) -> bool {
52    (3..=16).contains(&name.len())
53        && name
54            .chars()
55            .all(|character| character.is_ascii_alphanumeric() || character == '_')
56}
57
58/// Generates vanilla's deterministic offline-mode UUID for a player name.
59#[must_use]
60pub fn offline_uuid(username: &str) -> Uuid {
61    Builder::from_md5_bytes(md5::compute(format!("OfflinePlayer:{username}")).0)
62        .with_version(Version::Md5)
63        .with_variant(Variant::RFC4122)
64        .into_uuid()
65}
66
67#[cfg(test)]
68mod tests {
69    use super::{is_valid_player_name, offline_uuid};
70
71    #[test]
72    fn validates_vanilla_player_names() {
73        assert!(is_valid_player_name("Steve"));
74        assert!(is_valid_player_name("Alex_123"));
75        assert!(!is_valid_player_name("ab"));
76        assert!(!is_valid_player_name("name-with-dash"));
77        assert!(!is_valid_player_name("way_too_long_player_name"));
78    }
79
80    #[test]
81    fn offline_uuid_matches_vanilla_name_uuid() {
82        assert_eq!(
83            offline_uuid("Steve").to_string(),
84            "5627dd98-e6be-3c21-b8a8-e92344183641"
85        );
86    }
87}