src/main.rs

Tue, 08 Dec 2020 23:46:34 -0700

author
Glitchvid <Glitchvid@glitchvid.com>
date
Tue, 08 Dec 2020 23:46:34 -0700
changeset 1
ad88152b3c76
parent 0
79c63b4440ee
child 2
474f76259cf0
permissions
-rw-r--r--

Add .hgignore

use std::env;
use std::convert::TryFrom;
use regex::Regex;

const REGEX_STEAMID2: &str = r"^STEAM_([0-5]):([01]):(\d+$)";
const REGEX_STEAMID3: &str = r"^\[(.):([01]):(\d+)\]$";

/* Valve SteamID Format:
 *  A SteamID is just a packed 64-bit unsigned integer!
 * 
 * It consists of five parts, from least to most significant bit:
 *  1. Authentication Server    - 1 bit     (1)
 *  2. Account Number           - 31 bits   (32)
 *  3. Instance                 - 20 bits   (52)
 *  4. Account Type             - 4 bits    (56)
 *  5. Universe                 - 8 bits    (64)
 * 
 * This can be visualized like so:
 *  1. _______________________________________________________________X
 *  2. ________________________________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_
 *  3. ____________XXXXXXXXXXXXXXXXXXXX________________________________
 *  4. ________XXXX____________________________________________________
 *  5. XXXXXXXX________________________________________________________
 * 
 * There are multiple ways to express a SteamID, some are lossy.
 *  A. steamID64        - (1)+(2)+(3)+(4)+(5)
 *  B. steamID2         - STEAM_(5):(1):(2)
 *  C. steamID3         - [(4):(5):(1)+(2)]
*/

/*
 * Instance
 *  The Instance field nominally holds what 'instance' the steamID is, however
 *  when specifying a chatroom, the last 8 bits define the "type" of chatroom.
 * This can be visualized like so:
 *  ____________ZZZZZZZZXXXXXXXXXXXX
*/

fn account_type_to_char(account_type: u8, instance: Option<u32>) -> char {
    match account_type {
        0   => 'I', // Invalid
        1   => 'U', // Individual (Profiles)
        2   => 'M', // Multiseat
        3   => 'G', // GameServer
        4   => 'A', // AnonGameServer
        5   => 'P', // Pending
        6   => 'C', // ContentServer
        7   => 'g', // Clan (Groups)
        8   =>  match instance.unwrap_or(0) >> 12 & 255 { // EChatSteamIDInstanceFlags (Shifted 12 right)
                    1   => 'T', // MatchMaking Lobby
                    2   => 'L', // Lobby
                    4   => 'c', // Clan Chat
                    _   => 'c', // Clan Chat (Default)
                }
        //9   => '',// ConsoleUser
        10  => 'a', // AnonUser
        _   => 'I', // Invalid (Default)
    }
}

#[derive(Debug)]
enum SteamIDAccountType {
    Invalid,
    Individual,
    Multiseat,
    GameServer,
    AnonGameServer,
    Pending,
    ContentServer,
    Clan,
    Chat,
    ConsoleUser,
    AnonUser,
}

impl SteamIDAccountType {
    fn to_char(self, instance: Option<u32>) -> char {
        match self {
            SteamIDAccountType::Invalid         => 'I',
            SteamIDAccountType::Individual      => 'U',
            SteamIDAccountType::Multiseat       => 'M',
            SteamIDAccountType::GameServer      => 'G',
            SteamIDAccountType::AnonGameServer  => 'A',
            SteamIDAccountType::Pending         => 'P',
            SteamIDAccountType::ContentServer   => 'C',
            SteamIDAccountType::Clan            => 'g',
            SteamIDAccountType::Chat            =>
                    match instance.unwrap_or(0) >> 12 & 255 { // EChatSteamIDInstanceFlags (Shifted 12 right)
                        1   => 'T', // MatchMaking Lobby
                        2   => 'L', // Lobby
                        4   => 'c', // Clan Chat
                        _   => 'c', // Clan Chat (Default)
                    }
            SteamIDAccountType::ConsoleUser     => 'I',
            SteamIDAccountType::AnonUser        => 'a',
        }
    }
    fn to_int(self) -> u32 {
        match self {
            SteamIDAccountType::Invalid         => 0,
            SteamIDAccountType::Individual      => 1,
            SteamIDAccountType::Multiseat       => 2,
            SteamIDAccountType::GameServer      => 3,
            SteamIDAccountType::AnonGameServer  => 4,
            SteamIDAccountType::Pending         => 5,
            SteamIDAccountType::ContentServer   => 6,
            SteamIDAccountType::Clan            => 7,
            SteamIDAccountType::Chat            => 8,
            SteamIDAccountType::ConsoleUser     => 9,
            SteamIDAccountType::AnonUser        => 10,
        }
    }
    fn from_char(account_type: char) -> SteamIDAccountType {
        match account_type {
            'I'   => SteamIDAccountType::Invalid,
            'U'   => SteamIDAccountType::Individual,
            'M'   => SteamIDAccountType::Multiseat,
            'G'   => SteamIDAccountType::GameServer,
            'A'   => SteamIDAccountType::AnonGameServer,
            'P'   => SteamIDAccountType::Pending,
            'C'   => SteamIDAccountType::ContentServer,
            'g'   => SteamIDAccountType::Clan,
            'c'   => SteamIDAccountType::Chat,
            'T'   => SteamIDAccountType::Chat,
            'L'   => SteamIDAccountType::Chat,
            'a'   => SteamIDAccountType::AnonUser,
            _     => SteamIDAccountType::Invalid,
        }
    }
    fn from_int(account_type: u8) -> SteamIDAccountType {
        match account_type {
            0   => SteamIDAccountType::Invalid,
            1   => SteamIDAccountType::Individual,
            2   => SteamIDAccountType::Multiseat,
            3   => SteamIDAccountType::GameServer,
            4   => SteamIDAccountType::AnonGameServer,
            5   => SteamIDAccountType::Pending,
            6   => SteamIDAccountType::ContentServer,
            7   => SteamIDAccountType::Clan,
            8   => SteamIDAccountType::Chat,
            9   => SteamIDAccountType::ConsoleUser,
            10  => SteamIDAccountType::AnonUser,
            _   => SteamIDAccountType::Invalid,
        }
    }
}

#[derive(Debug, PartialEq)]
enum SteamIDType{
    SteamID64,
    SteamID2,
    SteamID3,
}

fn string_to_steamid_type(steamid: &str) -> Result<SteamIDType, &str> {
    if steamid.parse::<u64>().is_ok() {
        return Ok(SteamIDType::SteamID64)
    }
    let steamid2 = Regex::new(REGEX_STEAMID2).unwrap();
    if steamid2.is_match(steamid) {
        return Ok(SteamIDType::SteamID2)
    }
    
    let steamid3 = Regex::new(REGEX_STEAMID3).unwrap();
    if steamid3.is_match(steamid) {
        return Ok(SteamIDType::SteamID3)
    }
    Err("Unable to parse to any SteamID Format.")
}

fn steamid2_to_steamid64( steamid2: &str) -> u64 {
    let regex = Regex::new(REGEX_STEAMID2).unwrap();
    let captures = regex.captures(steamid2).unwrap();

    let universe = captures.get(1).unwrap().as_str().parse::<u64>().unwrap_or(1);
    let auth_server = captures.get(2).unwrap().as_str().parse::<u64>().unwrap();
    let account_id = captures.get(3).unwrap().as_str().parse::<u64>().unwrap();

    let steam64 = (universe << 56) | auth_server | (account_id << 1) | 76561197960265728;
    steam64
}

fn steamid3_to_steamid64( steamid3: &str) -> u64 {
    let regex = Regex::new(REGEX_STEAMID3).unwrap();
    let captures = regex.captures(steamid3).unwrap();

    let account_type = captures.get(1).unwrap().as_str().parse::<char>().unwrap_or('I');
    let account_type = u64::from(SteamIDAccountType::to_int(SteamIDAccountType::from_char(account_type)));

    let universe = captures.get(2).unwrap().as_str().parse::<u64>().unwrap_or(1);
    let account_id = captures.get(3).unwrap().as_str().parse::<u64>().unwrap();

    let steam64 = (account_type << 52) | (universe << 56) | account_id ;
    steam64
}

fn string_to_steamid64( input: &str) -> Result<u64, &str> {
    let steam_type = string_to_steamid_type(input);
    match steam_type {
        Ok(steam_type) => {
            match steam_type {
                SteamIDType::SteamID64 => return Ok(input.parse::<u64>().unwrap()),
                SteamIDType::SteamID2 => return Ok(steamid2_to_steamid64(input)),
                SteamIDType::SteamID3 => return Ok(steamid3_to_steamid64(input)),
            }
        }
        Err(steam_type) => return Err(steam_type)
    }
}

struct SteamID {
    account_id: u32,
    account_instance: u32,
    account_type: u8,
    account_universe: u8,
}

impl SteamID {
    fn new() -> SteamID {
        SteamID {
            account_id: 0,
            account_instance:  1,
            account_type: 1,
            account_universe: 1,
        }
    }
    fn set_steamid64(&mut self, steamid_64: u64) {
        self.account_id = u32::try_from(steamid_64 & 4294967295).unwrap_or(1);
        self.account_instance =  u32::try_from(steamid_64 >> 32 & 1048575).unwrap_or(1);
        self.account_type = u8::try_from(steamid_64 >> 52 & 15).unwrap_or(1);
        self.account_universe = u8::try_from(steamid_64 >> 56 & 15).unwrap_or(1);
    }
    fn get_steamid64(&self) -> u64 {
        u64::from(self.account_id) |
        u64::from(self.account_instance) << 32 |
        u64::from(self.account_type) << 52 |
        u64::from(self.account_universe)  << 56
    }
    fn get_steamid2(&self) -> String {
        let authserver: u32 = self.account_id & 1; // Ideally we'd cast this to a bool and convert that to a 0 or 1 later.
        let accountid: u32 = (self.account_id >> 1) & 2147483647;
        format!("STEAM_{}:{}:{}", self.account_universe, authserver, accountid)
    }
    fn get_steamid3(&self) -> String {
        let type_char: char = account_type_to_char(self.account_type, Some(self.account_instance));
        format!("[{}:{}:{}]", type_char, self.account_universe, self.account_id)
    }
}



fn main() {
    // Gather our CLI arguments
    let args: Vec<String> = env::args().collect();

    //println!("Args: {:?}", args);
    //println!("Len: {}", args.len());
   
    // Dumb check, make sure they even tried providing a SteamID
    if args.len() < 2 {
        println!("No IDs provided!");
        std::process::exit(-1);
    }

    // Process all of our passed strings
    for i in 1..args.len() {
        let input = &args[i];
        let steam_type = string_to_steamid_type(input);
        match steam_type {
            Ok(steam_type) => {
                println!("Interpreting as {:?}", steam_type );
                match steam_type {
                    SteamIDType::SteamID64 => {
                        let mut steamid_object = SteamID::new();
                        steamid_object.set_steamid64(input.parse::<u64>().expect("SteamID64 Not a Number!"));
                        println!("steamID64:\t{}", steamid_object.get_steamid64());
                        println!("steamID:  \t{}", steamid_object.get_steamid2());
                        println!("steamID3: \t{}", steamid_object.get_steamid3());
                    }
                    SteamIDType::SteamID2 => {
                        let mut steamid_object = SteamID::new();
                        steamid_object.set_steamid64(steamid2_to_steamid64(input));
                        println!("steamID64:\t{}", steamid_object.get_steamid64());
                        println!("steamID:  \t{}", steamid_object.get_steamid2());
                        println!("steamID3: \t{}", steamid_object.get_steamid3());
                    }
                    SteamIDType::SteamID3 => {
                        let mut steamid_object = SteamID::new();
                        steamid_object.set_steamid64(steamid3_to_steamid64(input));
                        println!("steamID64:\t{}", steamid_object.get_steamid64());
                        println!("steamID:  \t{}", steamid_object.get_steamid2());
                        println!("steamID3: \t{}", steamid_object.get_steamid3());
                    }
                }
            }
            Err(_steam_type) => {
                println!("Unable to interpret {}", input);
            }
        }
        println!("");
    }

    // println!("Generating Alias SteamID64s...");
    // thread::sleep(time::Duration::from_secs(1));
    // for n in 1..1048575 {
    //     let instance: u64 = n << 32;
    //     let newsteam64 = (steamid64 & 18442240478377148415) | instance;
    //     println!("http://steamcommunity.com/profiles/{}", newsteam64);
    // }
}

mercurial