demboyz/demboyz/base/steamid.h

458 lines
13 KiB
C++

/*!
* 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 <string>
#include <cctype>
/** ---------------------------------------------------------------------------
* 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