|
1 use std::env; |
|
2 use std::convert::TryFrom; |
|
3 use regex::Regex; |
|
4 |
|
5 const REGEX_STEAMID2: &str = r"^STEAM_([0-5]):([01]):(\d+$)"; |
|
6 const REGEX_STEAMID3: &str = r"^\[(.):([01]):(\d+)\]$"; |
|
7 |
|
8 /* Valve SteamID Format: |
|
9 * A SteamID is just a packed 64-bit unsigned integer! |
|
10 * |
|
11 * It consists of five parts, from least to most significant bit: |
|
12 * 1. Authentication Server - 1 bit (1) |
|
13 * 2. Account Number - 31 bits (32) |
|
14 * 3. Instance - 20 bits (52) |
|
15 * 4. Account Type - 4 bits (56) |
|
16 * 5. Universe - 8 bits (64) |
|
17 * |
|
18 * This can be visualized like so: |
|
19 * 1. _______________________________________________________________X |
|
20 * 2. ________________________________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_ |
|
21 * 3. ____________XXXXXXXXXXXXXXXXXXXX________________________________ |
|
22 * 4. ________XXXX____________________________________________________ |
|
23 * 5. XXXXXXXX________________________________________________________ |
|
24 * |
|
25 * There are multiple ways to express a SteamID, some are lossy. |
|
26 * A. steamID64 - (1)+(2)+(3)+(4)+(5) |
|
27 * B. steamID2 - STEAM_(5):(1):(2) |
|
28 * C. steamID3 - [(4):(5):(1)+(2)] |
|
29 */ |
|
30 |
|
31 /* |
|
32 * Instance |
|
33 * The Instance field nominally holds what 'instance' the steamID is, however |
|
34 * when specifying a chatroom, the last 8 bits define the "type" of chatroom. |
|
35 * This can be visualized like so: |
|
36 * ____________ZZZZZZZZXXXXXXXXXXXX |
|
37 */ |
|
38 |
|
39 fn account_type_to_char(account_type: u8, instance: Option<u32>) -> char { |
|
40 match account_type { |
|
41 0 => 'I', // Invalid |
|
42 1 => 'U', // Individual (Profiles) |
|
43 2 => 'M', // Multiseat |
|
44 3 => 'G', // GameServer |
|
45 4 => 'A', // AnonGameServer |
|
46 5 => 'P', // Pending |
|
47 6 => 'C', // ContentServer |
|
48 7 => 'g', // Clan (Groups) |
|
49 8 => match instance.unwrap_or(0) >> 12 & 255 { // EChatSteamIDInstanceFlags (Shifted 12 right) |
|
50 1 => 'T', // MatchMaking Lobby |
|
51 2 => 'L', // Lobby |
|
52 4 => 'c', // Clan Chat |
|
53 _ => 'c', // Clan Chat (Default) |
|
54 } |
|
55 //9 => '',// ConsoleUser |
|
56 10 => 'a', // AnonUser |
|
57 _ => 'I', // Invalid (Default) |
|
58 } |
|
59 } |
|
60 |
|
61 #[derive(Debug)] |
|
62 enum SteamIDAccountType { |
|
63 Invalid, |
|
64 Individual, |
|
65 Multiseat, |
|
66 GameServer, |
|
67 AnonGameServer, |
|
68 Pending, |
|
69 ContentServer, |
|
70 Clan, |
|
71 Chat, |
|
72 ConsoleUser, |
|
73 AnonUser, |
|
74 } |
|
75 |
|
76 impl SteamIDAccountType { |
|
77 fn to_char(self, instance: Option<u32>) -> char { |
|
78 match self { |
|
79 SteamIDAccountType::Invalid => 'I', |
|
80 SteamIDAccountType::Individual => 'U', |
|
81 SteamIDAccountType::Multiseat => 'M', |
|
82 SteamIDAccountType::GameServer => 'G', |
|
83 SteamIDAccountType::AnonGameServer => 'A', |
|
84 SteamIDAccountType::Pending => 'P', |
|
85 SteamIDAccountType::ContentServer => 'C', |
|
86 SteamIDAccountType::Clan => 'g', |
|
87 SteamIDAccountType::Chat => |
|
88 match instance.unwrap_or(0) >> 12 & 255 { // EChatSteamIDInstanceFlags (Shifted 12 right) |
|
89 1 => 'T', // MatchMaking Lobby |
|
90 2 => 'L', // Lobby |
|
91 4 => 'c', // Clan Chat |
|
92 _ => 'c', // Clan Chat (Default) |
|
93 } |
|
94 SteamIDAccountType::ConsoleUser => 'I', |
|
95 SteamIDAccountType::AnonUser => 'a', |
|
96 } |
|
97 } |
|
98 fn to_int(self) -> u32 { |
|
99 match self { |
|
100 SteamIDAccountType::Invalid => 0, |
|
101 SteamIDAccountType::Individual => 1, |
|
102 SteamIDAccountType::Multiseat => 2, |
|
103 SteamIDAccountType::GameServer => 3, |
|
104 SteamIDAccountType::AnonGameServer => 4, |
|
105 SteamIDAccountType::Pending => 5, |
|
106 SteamIDAccountType::ContentServer => 6, |
|
107 SteamIDAccountType::Clan => 7, |
|
108 SteamIDAccountType::Chat => 8, |
|
109 SteamIDAccountType::ConsoleUser => 9, |
|
110 SteamIDAccountType::AnonUser => 10, |
|
111 } |
|
112 } |
|
113 fn from_char(account_type: char) -> SteamIDAccountType { |
|
114 match account_type { |
|
115 'I' => SteamIDAccountType::Invalid, |
|
116 'U' => SteamIDAccountType::Individual, |
|
117 'M' => SteamIDAccountType::Multiseat, |
|
118 'G' => SteamIDAccountType::GameServer, |
|
119 'A' => SteamIDAccountType::AnonGameServer, |
|
120 'P' => SteamIDAccountType::Pending, |
|
121 'C' => SteamIDAccountType::ContentServer, |
|
122 'g' => SteamIDAccountType::Clan, |
|
123 'c' => SteamIDAccountType::Chat, |
|
124 'T' => SteamIDAccountType::Chat, |
|
125 'L' => SteamIDAccountType::Chat, |
|
126 'a' => SteamIDAccountType::AnonUser, |
|
127 _ => SteamIDAccountType::Invalid, |
|
128 } |
|
129 } |
|
130 fn from_int(account_type: u8) -> SteamIDAccountType { |
|
131 match account_type { |
|
132 0 => SteamIDAccountType::Invalid, |
|
133 1 => SteamIDAccountType::Individual, |
|
134 2 => SteamIDAccountType::Multiseat, |
|
135 3 => SteamIDAccountType::GameServer, |
|
136 4 => SteamIDAccountType::AnonGameServer, |
|
137 5 => SteamIDAccountType::Pending, |
|
138 6 => SteamIDAccountType::ContentServer, |
|
139 7 => SteamIDAccountType::Clan, |
|
140 8 => SteamIDAccountType::Chat, |
|
141 9 => SteamIDAccountType::ConsoleUser, |
|
142 10 => SteamIDAccountType::AnonUser, |
|
143 _ => SteamIDAccountType::Invalid, |
|
144 } |
|
145 } |
|
146 } |
|
147 |
|
148 #[derive(Debug, PartialEq)] |
|
149 enum SteamIDType{ |
|
150 SteamID64, |
|
151 SteamID2, |
|
152 SteamID3, |
|
153 } |
|
154 |
|
155 fn string_to_steamid_type(steamid: &str) -> Result<SteamIDType, &str> { |
|
156 if steamid.parse::<u64>().is_ok() { |
|
157 return Ok(SteamIDType::SteamID64) |
|
158 } |
|
159 let steamid2 = Regex::new(REGEX_STEAMID2).unwrap(); |
|
160 if steamid2.is_match(steamid) { |
|
161 return Ok(SteamIDType::SteamID2) |
|
162 } |
|
163 |
|
164 let steamid3 = Regex::new(REGEX_STEAMID3).unwrap(); |
|
165 if steamid3.is_match(steamid) { |
|
166 return Ok(SteamIDType::SteamID3) |
|
167 } |
|
168 Err("Unable to parse to any SteamID Format.") |
|
169 } |
|
170 |
|
171 fn steamid2_to_steamid64( steamid2: &str) -> u64 { |
|
172 let regex = Regex::new(REGEX_STEAMID2).unwrap(); |
|
173 let captures = regex.captures(steamid2).unwrap(); |
|
174 |
|
175 let universe = captures.get(1).unwrap().as_str().parse::<u64>().unwrap_or(1); |
|
176 let auth_server = captures.get(2).unwrap().as_str().parse::<u64>().unwrap(); |
|
177 let account_id = captures.get(3).unwrap().as_str().parse::<u64>().unwrap(); |
|
178 |
|
179 let steam64 = (universe << 56) | auth_server | (account_id << 1) | 76561197960265728; |
|
180 steam64 |
|
181 } |
|
182 |
|
183 fn steamid3_to_steamid64( steamid3: &str) -> u64 { |
|
184 let regex = Regex::new(REGEX_STEAMID3).unwrap(); |
|
185 let captures = regex.captures(steamid3).unwrap(); |
|
186 |
|
187 let account_type = captures.get(1).unwrap().as_str().parse::<char>().unwrap_or('I'); |
|
188 let account_type = u64::from(SteamIDAccountType::to_int(SteamIDAccountType::from_char(account_type))); |
|
189 |
|
190 let universe = captures.get(2).unwrap().as_str().parse::<u64>().unwrap_or(1); |
|
191 let account_id = captures.get(3).unwrap().as_str().parse::<u64>().unwrap(); |
|
192 |
|
193 let steam64 = (account_type << 52) | (universe << 56) | account_id ; |
|
194 steam64 |
|
195 } |
|
196 |
|
197 fn string_to_steamid64( input: &str) -> Result<u64, &str> { |
|
198 let steam_type = string_to_steamid_type(input); |
|
199 match steam_type { |
|
200 Ok(steam_type) => { |
|
201 match steam_type { |
|
202 SteamIDType::SteamID64 => return Ok(input.parse::<u64>().unwrap()), |
|
203 SteamIDType::SteamID2 => return Ok(steamid2_to_steamid64(input)), |
|
204 SteamIDType::SteamID3 => return Ok(steamid3_to_steamid64(input)), |
|
205 } |
|
206 } |
|
207 Err(steam_type) => return Err(steam_type) |
|
208 } |
|
209 } |
|
210 |
|
211 struct SteamID { |
|
212 account_id: u32, |
|
213 account_instance: u32, |
|
214 account_type: u8, |
|
215 account_universe: u8, |
|
216 } |
|
217 |
|
218 impl SteamID { |
|
219 fn new() -> SteamID { |
|
220 SteamID { |
|
221 account_id: 0, |
|
222 account_instance: 1, |
|
223 account_type: 1, |
|
224 account_universe: 1, |
|
225 } |
|
226 } |
|
227 fn set_steamid64(&mut self, steamid_64: u64) { |
|
228 self.account_id = u32::try_from(steamid_64 & 4294967295).unwrap_or(1); |
|
229 self.account_instance = u32::try_from(steamid_64 >> 32 & 1048575).unwrap_or(1); |
|
230 self.account_type = u8::try_from(steamid_64 >> 52 & 15).unwrap_or(1); |
|
231 self.account_universe = u8::try_from(steamid_64 >> 56 & 15).unwrap_or(1); |
|
232 } |
|
233 fn get_steamid64(&self) -> u64 { |
|
234 u64::from(self.account_id) | |
|
235 u64::from(self.account_instance) << 32 | |
|
236 u64::from(self.account_type) << 52 | |
|
237 u64::from(self.account_universe) << 56 |
|
238 } |
|
239 fn get_steamid2(&self) -> String { |
|
240 let authserver: u32 = self.account_id & 1; // Ideally we'd cast this to a bool and convert that to a 0 or 1 later. |
|
241 let accountid: u32 = (self.account_id >> 1) & 2147483647; |
|
242 format!("STEAM_{}:{}:{}", self.account_universe, authserver, accountid) |
|
243 } |
|
244 fn get_steamid3(&self) -> String { |
|
245 let type_char: char = account_type_to_char(self.account_type, Some(self.account_instance)); |
|
246 format!("[{}:{}:{}]", type_char, self.account_universe, self.account_id) |
|
247 } |
|
248 } |
|
249 |
|
250 |
|
251 |
|
252 fn main() { |
|
253 // Gather our CLI arguments |
|
254 let args: Vec<String> = env::args().collect(); |
|
255 |
|
256 //println!("Args: {:?}", args); |
|
257 //println!("Len: {}", args.len()); |
|
258 |
|
259 // Dumb check, make sure they even tried providing a SteamID |
|
260 if args.len() < 2 { |
|
261 println!("No IDs provided!"); |
|
262 std::process::exit(-1); |
|
263 } |
|
264 |
|
265 // Process all of our passed strings |
|
266 for i in 1..args.len() { |
|
267 let input = &args[i]; |
|
268 let steam_type = string_to_steamid_type(input); |
|
269 match steam_type { |
|
270 Ok(steam_type) => { |
|
271 println!("Interpreting as {:?}", steam_type ); |
|
272 match steam_type { |
|
273 SteamIDType::SteamID64 => { |
|
274 let mut steamid_object = SteamID::new(); |
|
275 steamid_object.set_steamid64(input.parse::<u64>().expect("SteamID64 Not a Number!")); |
|
276 println!("steamID64:\t{}", steamid_object.get_steamid64()); |
|
277 println!("steamID: \t{}", steamid_object.get_steamid2()); |
|
278 println!("steamID3: \t{}", steamid_object.get_steamid3()); |
|
279 } |
|
280 SteamIDType::SteamID2 => { |
|
281 let mut steamid_object = SteamID::new(); |
|
282 steamid_object.set_steamid64(steamid2_to_steamid64(input)); |
|
283 println!("steamID64:\t{}", steamid_object.get_steamid64()); |
|
284 println!("steamID: \t{}", steamid_object.get_steamid2()); |
|
285 println!("steamID3: \t{}", steamid_object.get_steamid3()); |
|
286 } |
|
287 SteamIDType::SteamID3 => { |
|
288 let mut steamid_object = SteamID::new(); |
|
289 steamid_object.set_steamid64(steamid3_to_steamid64(input)); |
|
290 println!("steamID64:\t{}", steamid_object.get_steamid64()); |
|
291 println!("steamID: \t{}", steamid_object.get_steamid2()); |
|
292 println!("steamID3: \t{}", steamid_object.get_steamid3()); |
|
293 } |
|
294 } |
|
295 } |
|
296 Err(_steam_type) => { |
|
297 println!("Unable to interpret {}", input); |
|
298 } |
|
299 } |
|
300 println!(""); |
|
301 } |
|
302 |
|
303 // println!("Generating Alias SteamID64s..."); |
|
304 // thread::sleep(time::Duration::from_secs(1)); |
|
305 // for n in 1..1048575 { |
|
306 // let instance: u64 = n << 32; |
|
307 // let newsteam64 = (steamid64 & 18442240478377148415) | instance; |
|
308 // println!("http://steamcommunity.com/profiles/{}", newsteam64); |
|
309 // } |
|
310 } |