1const ACCOUNT_ID_MASK: u64 = 0xFFFFFFFF;
2const ACCOUNT_INSTANCE_MASK: u64 = 0x000FFFFF;
3
4#[derive(PartialEq, Debug)]
5pub enum Universe {
6 Invalid = 0,
7 Public = 1,
8 Beta = 2,
9 Internal = 3,
10 Dev = 4,
11}
12
13#[derive(PartialEq, Debug)]
14pub enum Type {
15 Invalid = 0,
16 Individual = 1,
17 Multiseat = 2,
18 GameServer = 3,
19 AnonGameServer = 4,
20 Pending = 5,
21 ContentServer = 6,
22 Clan = 7,
23 Chat = 8,
24 P2PSuperSeeder = 9,
25 AnonUser = 10,
26}
27
28#[derive(PartialEq, Debug)]
29pub enum Instance {
30 All = 0,
31 Desktop = 1,
32 Console = 2,
33 Web = 4,
34}
35
36#[derive(PartialEq, Debug)]
37pub struct SteamID {
38 universe: Universe,
39 type_: Type,
40 instance: Instance,
41 account_id: u32,
42}
43
44impl Universe {
45 fn from_u32(value: u32) -> Option<Universe> {
46 match value {
47 0 => Some(Universe::Invalid),
48 1 => Some(Universe::Public),
49 2 => Some(Universe::Beta),
50 3 => Some(Universe::Internal),
51 4 => Some(Universe::Dev),
52 _ => None,
53 }
54 }
55}
56
57impl Type {
58 fn from_u32(value: u32) -> Option<Type> {
59 match value {
60 0 => Some(Type::Invalid),
61 1 => Some(Type::Individual),
62 2 => Some(Type::Multiseat),
63 3 => Some(Type::GameServer),
64 4 => Some(Type::AnonGameServer),
65 5 => Some(Type::Pending),
66 6 => Some(Type::ContentServer),
67 7 => Some(Type::Clan),
68 8 => Some(Type::Chat),
69 9 => Some(Type::P2PSuperSeeder),
70 10 => Some(Type::AnonUser),
71 _ => None,
72 }
73 }
74}
75
76impl Instance {
77 fn from_u32(value: u32) -> Option<Instance> {
78 match value {
79 0 => Some(Instance::All),
80 1 => Some(Instance::Desktop),
81 2 => Some(Instance::Console),
82 4 => Some(Instance::Web),
83 _ => None,
84 }
85 }
86}
87
88impl SteamID {
89 pub fn new(input: &str) -> Option<Self> {
100 let mut id = Self {
101 universe: Universe::Invalid,
102 type_: Type::Invalid,
103 instance: Instance::All,
104 account_id: 0,
105 };
106
107 if let Some(id64) = SteamID::validate_steam64(input) {
108 id.universe = Universe::from_u32((id64 >> 56) as u32).unwrap_or(Universe::Invalid);
109 id.type_ = Type::from_u32(((id64 >> 52) & 0xF) as u32).unwrap_or(Type::Invalid);
110 id.instance = Instance::from_u32(((id64 >> 32) & ACCOUNT_INSTANCE_MASK) as u32)
111 .unwrap_or(Instance::All);
112 id.account_id = (id64 & ACCOUNT_ID_MASK) as u32;
113 } else if let Some(id2) = SteamID::validate_steam2(input) {
114 let mut parts = id2.split(':');
115
116 parts.next();
117 let universe = parts.next().unwrap();
118 let account_id = parts.next().unwrap();
119
120 id.type_ = Type::Individual;
121 id.instance = Instance::Desktop;
122 id.account_id = account_id.parse::<u32>().unwrap();
123 id.universe = match universe.parse::<u32>().unwrap() {
124 0 => Universe::Public, _ => {
126 Universe::from_u32(universe.parse::<u32>().unwrap()).unwrap_or(Universe::Public)
127 }
128 }
129 } else {
130 return None;
131 }
132
133 Some(id)
134 }
135
136 pub fn render_as_steam2<'a>(self) -> Option<String> {
145 if self.type_ != Type::Individual {
146 return None;
147 }
148
149 let mut universe = self.universe as u32;
150
151 if universe == 1 {
152 universe = 0;
153 }
154
155 Some(format!(
156 "STEAM_{}:{}:{}",
157 universe,
158 self.instance as u32,
159 ((self.account_id / 2) as f64).floor()
160 ))
161 }
162
163 pub fn validate_steam2(input: &str) -> Option<&str> {
179 let mut parts = input.split(':');
182
183 if parts.clone().count() != 3 {
184 return None;
185 }
186
187 let universe = parts.next().unwrap();
188
189 if universe != "STEAM_0" && universe != "STEAM_1" {
190 return None;
191 }
192
193 Some(input)
194 }
195
196 pub fn validate_steam64(input: &str) -> Option<u64> {
206 if input.len() == 17 {
207 if let Ok(id) = u64::from_str_radix(input, 10) {
208 if (id & ACCOUNT_ID_MASK) != 0 {
209 return Some(id);
210 }
211 }
212 }
213
214 None
215 }
216}