/*! * SteamID Parser * * Copyright 2014 Mukunda Johnson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma once #ifndef _STEAMID_ #define _STEAMID_ #include #include /** --------------------------------------------------------------------------- * SteamID * * Contains a User Steam ID. * * @author Mukunda Johnson */ class SteamID { public: enum class Formats { AUTO = 0, // Auto-detect format --- this also supports // other unlisted formats such as // full profile URLs. STEAMID32 = 1, // Classic STEAM_x:y:zzzzzz | x = 0/1 STEAMID64 = 2, // SteamID64: 7656119xxxxxxxxxx STEAMID3 = 3, // SteamID3 format: [U:1:xxxxxx] S32 = 4, // Raw 32-bit SIGNED format. // this is a raw steamid index that overflows // into negative bitspace. // This is the format that SourceMod returns // with GetSteamAccountID, and will always // fit into a 32-bit signed variable. (e.g. // a 32-bit PHP integer). RAW = 5, // Raw index. like 64-bit minus the base value. }; // 64-bit type. using bigint = long long; using uint = unsigned int; // base constant of 64-bit Steam IDs static const bigint STEAMID64_BASE = 76561197960265728L; // max allowed value. (sanity check) // 2^36; update this in approx 2,400,000 years static const bigint MAX_VALUE = 68719476736L; /** ----------------------------------------------------------------------- * Parse a Steam ID. * * @param input Input to parse. * * @param format Input formatting, see Format constants. * Defaults to Format::AUTO which detects the format. * * @param detect_raw Detect and parse RAW values. (only used with * Format::AUTO. e.g "123" will resolve to the * SteamID with the raw value 123. * Default option set with ParseRawDefault. * * @returns SteamID instance or an empty SteamID if the parsing fails. */ static SteamID Parse( const std::string &input, Formats format = Formats::AUTO, int detect_raw = ParseRawDefault(-1) ) { if( input.empty() ) return SteamID(); // no input... try { switch( format ) { //----------------------------------------------------------------- case Formats::STEAMID32: { // regex is slow as fuck for some reason. if( input.size() < 11 || input[0] != 'S' || input[1] != 'T' || input[2] != 'E' || input[3] != 'A' || input[4] != 'M' || input[5] != '_' || !Is01(input,6) || input[7] != ':' || !Is01(input,8) || input[9] != ':' ) return SteamID(); // STEAM_X:Y:Z' // static const std::regex r( // R"--(^STEAM_[0-1]:[0-1]:[0-9]+$)--", // rc::icase | rc::optimize ); // // if( !std::regex_match( input, r ) ) return SteamID(); bigint z = std::stoll( input.substr( 10 ) ); z = (z << 1) + (input[8] - '0'); SteamID result(z); //result.Cache( Format::STEAMID32, input ); return result; //----------------------------------------------------------------- } case Formats::STEAMID64: { // allow digits only if( !IsDigits( input ) ) return SteamID(); // convert to raw (subtract base) SteamID result( std::stoll( input ) - STEAMID64_BASE ); //result.Cache( Format::STEAMID64, input ); return result; //----------------------------------------------------------------- } case Formats::STEAMID3: { // [U:1:xxxxxx] if( input.size() < 7 || input[0] != '[' || input[1] != 'U' || input[2] != ':' || input[3] != '1' || input[4] != ':' || input[input.size()-1] != ']' || !IsDigits( input, 5, input.size() - 1 - 5 ) ) { return SteamID(); } // slow. // static const std::regex r( R"--(^\[U:1:[0-9]+\]$)--", // rc::optimize ); // if( !std::regex_match( input, r ) ) return SteamID(); SteamID result( std::stoll( input.substr( 5, input.size() - 1 - 5 ))); //result.Cache( Format::STEAMID3, input ); return result; //----------------------------------------------------------------- } case Formats::S32: { // signed digits if( !IsDigits( input, input[0] == '-' ? 1:0 ) ) { return SteamID(); } bigint a = std::stoll( input ); if( a < 0 ) a += 4294967296L; SteamID result( a ); //result.Cache( Format::S32, input ); return result; //----------------------------------------------------------------- } case Formats::RAW: { // validate digits only if( !IsDigits( input ) ) return SteamID(); return SteamID( std::stoll( input )); } case Formats::AUTO: { break; }} } catch( std::out_of_range& ) { // integer conversion out of range... return SteamID(); } // Auto detect format: std::string cleaned = TrimString(input); SteamID result; result = Parse( cleaned, Formats::STEAMID32 ); if( *result ) return result; result = Parse( cleaned, Formats::STEAMID64 ); if( *result ) return result; result = Parse( cleaned, Formats::STEAMID3 ); if( *result ) return result; result = TryConvertProfileURL( cleaned ); if( *result ) return result; // static const std::regex r_url( // R"--(^(?:https?:\/\/)?(?:www.)?steamcommunity.com\/profiles\/([0-9]+)$)--", // rc::icase | rc::optimize ); // std::smatch matches; // if( std::regex_match( cleaned, matches, r_url ) ) { // result = Parse( matches[1], Formats::STEAMID64 ); // if( *result ) return result; // } if( detect_raw ) { result = Parse( input, Formats::S32 ); if( *result ) return result; result = Parse( input, Formats::RAW ); if( *result ) return result; } // unknown stem return SteamID(); } /** ----------------------------------------------------------------------- * Format this SteamID to a string. * * @param format Output format. See Format constants. * @returns Formatted Steam ID, or an empty string if an invalid * format is given or the desired format cannot * contain the SteamID. */ std::string Format( Formats format ) const { switch( format ) { case Formats::STEAMID32: { bigint z = m_value >> 1; int y = m_value & 1; return std::string("STEAM_1:") + std::to_string(y) + ":" + std::to_string(z); } case Formats::STEAMID64: { return std::to_string( m_value + STEAMID64_BASE ); } case Formats::STEAMID3: { return std::string( "[U:1:" ) + std::to_string(m_value) + ']'; } case Formats::S32: { if( m_value >= 4294967296L ) { return ""; // too large for s32. } if( m_value >= 2147483648L ) { return std::to_string( m_value - 4294967296L ); } // --> } case Formats::RAW: { return std::to_string( m_value ); } case Formats::AUTO: { break; }} return ""; } /** ----------------------------------------------------------------------- * Set the default setting for detect_raw for Parse() * * @param detect_raw Default detect_raw value, see Parse function. * @returns Current or updated setting. */ static bool ParseRawDefault( int detect_raw = -1 ) { static int option = false; if( detect_raw == -1 ) return !!option; option = !!detect_raw; return !!option; } /** ----------------------------------------------------------------------- * Overload for Format. */ std::string operator[]( Formats format ) const { return Format( format ); } /** ----------------------------------------------------------------------- * Get raw value. 0 = empty */ bigint Value() const { return m_value; } /** ----------------------------------------------------------------------- * Get raw value. 0 = empty */ bigint operator*() const { return m_value; } /** ----------------------------------------------------------------------- * Returns true if this SteamID is empty/invalid. */ bool operator!() { return m_value == 0; } /** ----------------------------------------------------------------------- * Returns true if this SteamID is empty/invalid. */ bool Empty() const { return m_value == 0; } /** ----------------------------------------------------------------------- * Get 64-bit Steam ID. */ bigint To64() { return m_value + STEAMID64_BASE; } /** ----------------------------------------------------------------------- * Get raw value, same as operator*. */ bigint ToRaw() { return m_value; } /** ----------------------------------------------------------------------- * Get 32-bit value cast to signed. */ int ToS32() { if( m_value > 0xFFFFFFFF ) { return 0; } return (int)m_value; } /** ----------------------------------------------------------------------- * Parsing shortcut. */ SteamID( const std::string &input, Formats format = Formats::AUTO, int detect_raw = ParseRawDefault() ) : SteamID( Parse( input, format, detect_raw )) { } /** ----------------------------------------------------------------------- * Construct a Steam ID. * * @param raw RAW value of Steam ID. */ SteamID( bigint raw ) : m_value( (raw > 0 && raw <= MAX_VALUE) ? raw : 0 ) { } /** ----------------------------------------------------------------------- * An empty steam id. */ SteamID() : m_value(0) { } SteamID( const SteamID& o ) = default; SteamID( SteamID&& o ) { m_value = o.m_value; } SteamID& operator=( const SteamID& o ) = default; SteamID& operator=( SteamID&& o ) { m_value = o.m_value; return *this; } private: bigint m_value; // RAW Steam ID value. //------------------------------------------------------------------------- static bool IsDigits( const std::string &str, size_t start = 0, size_t length = 9000 ) { for( size_t i = start; i != (start+length) && str[i]; i++ ) { if( str[i] < '0' || str[i] > '9' ) return false; } return true; } //------------------------------------------------------------------------- static bool Is01( const std::string &str, size_t index ) { return str[index] == '0' || str[index] == '1'; } //------------------------------------------------------------------------- static std::string TrimString( const std::string &input ) { int start = 0, end = (int)input.size()-1; if( end < 0 ) return ""; while( std::isspace( input[start] )) { start++; if( start == (int)input.size() ) return ""; } while( std::isspace( input[end] )) { end--; } return input.substr( start, 1+end-start ); } //------------------------------------------------------------------------- static SteamID TryConvertProfileURL( std::string &str ) { if( str[0] != 'h' && str[0] != 'w' && str[0] != 's' ) return SteamID(); int lastslash = str.find_last_of( '/' ); if( lastslash == (int)std::string::npos ) return SteamID(); if( lastslash == (int)str.size()-1 ) { str.pop_back(); lastslash = str.find_last_of( '/' ); if( lastslash == (int)std::string::npos ) return SteamID(); } if( CheckProfilePrefix( str, lastslash ) ) { return Parse( str.substr( lastslash+1 ) ); } return SteamID(); } static bool CheckProfilePrefix( std::string &str, int end ) { // possible prefixes: // 0123456789012345678901234567890123456789 // https://www.steamcommunity.com/profiles/ // http://www.steamcommunity.com/profiles/ // https://steamcommunity.com/profiles/ // http://steamcommunity.com/profiles/ // www.steamcommunity.com/profiles/ // steamcommunity.com/profiles/ if( end == 39 ) { return str.compare( 0, 1+end, "https://www.steamcommunity.com/profiles/" ) == 0; } else if( end == 38 ) { return str.compare( 0, 1+end, "http://www.steamcommunity.com/profiles/" ) == 0; } else if( end == 35 ) { return str.compare( 0, 1+end, "https://steamcommunity.com/profiles/" ) == 0; } else if( end == 34 ) { return str.compare( 0, 1+end, "http://steamcommunity.com/profiles/" ) == 0; } else if( end == 31 ) { return str.compare( 0, 1+end, "www.steamcommunity.com/profiles/" ) == 0; } else if( end == 27 ) { return str.compare( 0, 1+end, "steamcommunity.com/profiles/" ) == 0; } return false; } }; #endif