Tue, 08 Dec 2020 23:44:49 -0700
Initial
0 | 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 | } |