Skip to main content

bedrock_world/
player.rs

1//! Player record identifiers and raw player payload helpers.
2
3use crate::error::Result;
4use crate::nbt::{NbtTag, parse_root_nbt, serialize_root_nbt};
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7use std::borrow::Cow;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10/// Player identifier as stored by Bedrock.
11pub enum PlayerId {
12    /// Local single-player record stored under `~local_player`.
13    Local,
14    /// Xbox user id record stored under `player_<xuid>`.
15    Xuid(String),
16    /// Legacy player data embedded in `level.dat`.
17    LegacyLevelDat,
18    /// Player-like identifier that is not backed by a known `LevelDB` key.
19    Unknown(String),
20}
21
22impl PlayerId {
23    #[must_use]
24    /// Encodes this value as its Bedrock storage key.
25    pub fn storage_key(&self) -> Option<Cow<'_, [u8]>> {
26        match self {
27            Self::Local => Some(Cow::Borrowed(b"~local_player")),
28            Self::Xuid(xuid) => Some(Cow::Owned(format!("player_{xuid}").into_bytes())),
29            Self::LegacyLevelDat | Self::Unknown(_) => None,
30        }
31    }
32
33    #[must_use]
34    /// Decodes a Bedrock player storage key.
35    pub fn from_storage_key(key: &[u8]) -> Option<Self> {
36        if key == b"~local_player" {
37            return Some(Self::Local);
38        }
39        let text = std::str::from_utf8(key).ok()?;
40        text.strip_prefix("player_")
41            .map(|xuid| Self::Xuid(xuid.to_string()))
42    }
43}
44
45#[derive(Debug, Clone, PartialEq)]
46/// Decoded player record with both structured NBT and original raw bytes.
47pub struct PlayerData {
48    /// Player record id.
49    pub id: PlayerId,
50    /// Parsed root NBT.
51    pub nbt: NbtTag,
52    /// Raw bytes as stored in `LevelDB`.
53    pub raw: Bytes,
54}
55
56impl PlayerData {
57    /// Parses a raw player payload into structured NBT while retaining bytes.
58    pub fn from_raw(id: PlayerId, raw: Bytes) -> Result<Self> {
59        let nbt = parse_root_nbt(&raw)?;
60        Ok(Self { id, nbt, raw })
61    }
62
63    /// Serializes structured NBT into a raw player payload.
64    pub fn from_nbt(id: PlayerId, nbt: NbtTag) -> Result<Self> {
65        let raw = Bytes::from(serialize_root_nbt(&nbt)?);
66        Ok(Self { id, nbt, raw })
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn player_keys_roundtrip() {
76        assert_eq!(
77            PlayerId::from_storage_key(b"~local_player"),
78            Some(PlayerId::Local)
79        );
80        assert_eq!(
81            PlayerId::from_storage_key(b"player_123"),
82            Some(PlayerId::Xuid("123".to_string()))
83        );
84        assert_eq!(
85            PlayerId::Xuid("123".to_string()).storage_key().as_deref(),
86            Some(&b"player_123"[..])
87        );
88    }
89}