├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock └── src └── SteamID.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexander Corn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-steamid 2 | SteamID class for PHP. The class is documented with phpdoc, just read it to find out how to use it. 3 | 4 | Works on both 32 and 64-bit PHP. 5 | 6 | [node.js version is also available](https://www.npmjs.com/package/steamid), which can be browserified pretty easily. 7 | 8 | # Example 9 | 10 | ```php 11 | getSteam2RenderedID() . PHP_EOL; 19 | echo "SteamID3: " . $steamid->getSteam3RenderedID() . PHP_EOL; 20 | echo "SteamID64: " . $steamid->getSteamID64() . PHP_EOL; 21 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doctormckay/steamid", 3 | "description": "PHP SteamID Manipulation", 4 | "type": "library", 5 | "require": { 6 | "phpseclib/phpseclib": "^3.0.10" 7 | }, 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Alexander Corn", 12 | "email": "mckay@doctormckay.com" 13 | }, 14 | { 15 | "name": "Nikki", 16 | "email": "nikki@nikkii.us" 17 | } 18 | ], 19 | "autoload": { 20 | "psr-4": { 21 | "SteamID\\": "src/" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d8ba3a681f71ebd473c6776c229a69a4", 8 | "packages": [ 9 | { 10 | "name": "paragonie/constant_time_encoding", 11 | "version": "v2.4.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/paragonie/constant_time_encoding.git", 15 | "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", 20 | "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7|^8" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^6|^7|^8|^9", 28 | "vimeo/psalm": "^1|^2|^3|^4" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-4": { 33 | "ParagonIE\\ConstantTime\\": "src/" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "MIT" 39 | ], 40 | "authors": [ 41 | { 42 | "name": "Paragon Initiative Enterprises", 43 | "email": "security@paragonie.com", 44 | "homepage": "https://paragonie.com", 45 | "role": "Maintainer" 46 | }, 47 | { 48 | "name": "Steve 'Sc00bz' Thomas", 49 | "email": "steve@tobtu.com", 50 | "homepage": "https://www.tobtu.com", 51 | "role": "Original Developer" 52 | } 53 | ], 54 | "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", 55 | "keywords": [ 56 | "base16", 57 | "base32", 58 | "base32_decode", 59 | "base32_encode", 60 | "base64", 61 | "base64_decode", 62 | "base64_encode", 63 | "bin2hex", 64 | "encoding", 65 | "hex", 66 | "hex2bin", 67 | "rfc4648" 68 | ], 69 | "support": { 70 | "email": "info@paragonie.com", 71 | "issues": "https://github.com/paragonie/constant_time_encoding/issues", 72 | "source": "https://github.com/paragonie/constant_time_encoding" 73 | }, 74 | "time": "2020-12-06T15:14:20+00:00" 75 | }, 76 | { 77 | "name": "paragonie/random_compat", 78 | "version": "v9.99.100", 79 | "source": { 80 | "type": "git", 81 | "url": "https://github.com/paragonie/random_compat.git", 82 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" 83 | }, 84 | "dist": { 85 | "type": "zip", 86 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", 87 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", 88 | "shasum": "" 89 | }, 90 | "require": { 91 | "php": ">= 7" 92 | }, 93 | "require-dev": { 94 | "phpunit/phpunit": "4.*|5.*", 95 | "vimeo/psalm": "^1" 96 | }, 97 | "suggest": { 98 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 99 | }, 100 | "type": "library", 101 | "notification-url": "https://packagist.org/downloads/", 102 | "license": [ 103 | "MIT" 104 | ], 105 | "authors": [ 106 | { 107 | "name": "Paragon Initiative Enterprises", 108 | "email": "security@paragonie.com", 109 | "homepage": "https://paragonie.com" 110 | } 111 | ], 112 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 113 | "keywords": [ 114 | "csprng", 115 | "polyfill", 116 | "pseudorandom", 117 | "random" 118 | ], 119 | "support": { 120 | "email": "info@paragonie.com", 121 | "issues": "https://github.com/paragonie/random_compat/issues", 122 | "source": "https://github.com/paragonie/random_compat" 123 | }, 124 | "time": "2020-10-15T08:29:30+00:00" 125 | }, 126 | { 127 | "name": "phpseclib/phpseclib", 128 | "version": "3.0.10", 129 | "source": { 130 | "type": "git", 131 | "url": "https://github.com/phpseclib/phpseclib.git", 132 | "reference": "62fcc5a94ac83b1506f52d7558d828617fac9187" 133 | }, 134 | "dist": { 135 | "type": "zip", 136 | "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/62fcc5a94ac83b1506f52d7558d828617fac9187", 137 | "reference": "62fcc5a94ac83b1506f52d7558d828617fac9187", 138 | "shasum": "" 139 | }, 140 | "require": { 141 | "paragonie/constant_time_encoding": "^1|^2", 142 | "paragonie/random_compat": "^1.4|^2.0|^9.99.99", 143 | "php": ">=5.6.1" 144 | }, 145 | "require-dev": { 146 | "phing/phing": "~2.7", 147 | "phpunit/phpunit": "^5.7|^6.0|^9.4", 148 | "squizlabs/php_codesniffer": "~2.0" 149 | }, 150 | "suggest": { 151 | "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", 152 | "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", 153 | "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", 154 | "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." 155 | }, 156 | "type": "library", 157 | "autoload": { 158 | "files": [ 159 | "phpseclib/bootstrap.php" 160 | ], 161 | "psr-4": { 162 | "phpseclib3\\": "phpseclib/" 163 | } 164 | }, 165 | "notification-url": "https://packagist.org/downloads/", 166 | "license": [ 167 | "MIT" 168 | ], 169 | "authors": [ 170 | { 171 | "name": "Jim Wigginton", 172 | "email": "terrafrost@php.net", 173 | "role": "Lead Developer" 174 | }, 175 | { 176 | "name": "Patrick Monnerat", 177 | "email": "pm@datasphere.ch", 178 | "role": "Developer" 179 | }, 180 | { 181 | "name": "Andreas Fischer", 182 | "email": "bantu@phpbb.com", 183 | "role": "Developer" 184 | }, 185 | { 186 | "name": "Hans-Jürgen Petrich", 187 | "email": "petrich@tronic-media.com", 188 | "role": "Developer" 189 | }, 190 | { 191 | "name": "Graham Campbell", 192 | "email": "graham@alt-three.com", 193 | "role": "Developer" 194 | } 195 | ], 196 | "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", 197 | "homepage": "http://phpseclib.sourceforge.net", 198 | "keywords": [ 199 | "BigInteger", 200 | "aes", 201 | "asn.1", 202 | "asn1", 203 | "blowfish", 204 | "crypto", 205 | "cryptography", 206 | "encryption", 207 | "rsa", 208 | "security", 209 | "sftp", 210 | "signature", 211 | "signing", 212 | "ssh", 213 | "twofish", 214 | "x.509", 215 | "x509" 216 | ], 217 | "support": { 218 | "issues": "https://github.com/phpseclib/phpseclib/issues", 219 | "source": "https://github.com/phpseclib/phpseclib/tree/3.0.10" 220 | }, 221 | "funding": [ 222 | { 223 | "url": "https://github.com/terrafrost", 224 | "type": "github" 225 | }, 226 | { 227 | "url": "https://www.patreon.com/phpseclib", 228 | "type": "patreon" 229 | }, 230 | { 231 | "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", 232 | "type": "tidelift" 233 | } 234 | ], 235 | "time": "2021-08-16T04:24:45+00:00" 236 | } 237 | ], 238 | "packages-dev": [], 239 | "aliases": [], 240 | "minimum-stability": "stable", 241 | "stability-flags": [], 242 | "prefer-stable": false, 243 | "prefer-lowest": false, 244 | "platform": [], 245 | "platform-dev": [], 246 | "plugin-api-version": "2.1.0" 247 | } 248 | -------------------------------------------------------------------------------- /src/SteamID.php: -------------------------------------------------------------------------------- 1 | 'I', 75 | self::TYPE_INDIVIDUAL => 'U', 76 | self::TYPE_MULTISEAT => 'M', 77 | self::TYPE_GAMESERVER => 'G', 78 | self::TYPE_ANON_GAMESERVER => 'A', 79 | self::TYPE_PENDING => 'P', 80 | self::TYPE_CONTENT_SERVER => 'C', 81 | self::TYPE_CLAN => 'g', 82 | self::TYPE_CHAT => 'T', 83 | self::TYPE_ANON_USER => 'a' 84 | ]; 85 | 86 | /** 87 | * @param null|string|int $id 88 | * @throws Exception If the input format was not recognized as a SteamID 89 | */ 90 | public function __construct($id = null) { 91 | if (!$id) { 92 | $this->universe = self::UNIVERSE_INVALID; 93 | $this->type = self::TYPE_INVALID; 94 | $this->instance = self::INSTANCE_ALL; 95 | $this->accountid = 0; 96 | return; 97 | } 98 | 99 | if (preg_match('/^STEAM_([0-5]):([0-1]):([0-9]+)$/', $id, $matches)) { 100 | // Steam2 ID 101 | if ($matches[1] <= self::UNIVERSE_PUBLIC) { 102 | $this->universe = self::UNIVERSE_PUBLIC; 103 | } else { 104 | $this->universe = (int) $matches[1]; 105 | } 106 | 107 | $this->type = self::TYPE_INDIVIDUAL; 108 | $this->instance = self::INSTANCE_DESKTOP; 109 | $this->accountid = (int) ($matches[3] * 2) + $matches[2]; 110 | } elseif (preg_match('/^\\[([a-zA-Z]):([0-5]):([0-9]+)(:[0-9]+)?\\]$/', $id, $matches)) { 111 | // Steam3 ID 112 | $this->universe = (int) $matches[2]; 113 | $this->accountid = (int) $matches[3]; 114 | 115 | $type_char = $matches[1]; 116 | 117 | if (!empty($matches[4])) { 118 | $this->instance = (int) substr($matches[4], 1); 119 | } else { 120 | switch ($type_char) { 121 | case 'g': 122 | case 'T': 123 | case 'c': 124 | case 'L': 125 | $this->instance = self::INSTANCE_ALL; 126 | break; 127 | 128 | default: 129 | $this->instance = self::INSTANCE_DESKTOP; 130 | } 131 | } 132 | 133 | if ($type_char == 'c') { 134 | $this->instance |= self::CHAT_INSTANCE_FLAG_CLAN; 135 | $this->type = self::TYPE_CHAT; 136 | } elseif ($type_char == 'L') { 137 | $this->instance |= self::CHAT_INSTANCE_FLAG_LOBBY; 138 | $this->type = self::TYPE_CHAT; 139 | } else { 140 | $this->type = self::getTypeFromChar($type_char); 141 | } 142 | } elseif (!is_numeric($id)) { 143 | throw new Exception("Unknown ID format"); 144 | } else { 145 | // SteamID64 146 | if (PHP_INT_SIZE == 4) { 147 | // Wrapper for BigInteger 148 | $bigint = new BigInteger($id); 149 | $this->universe = (int) $bigint->bitwise_rightShift(56)->toString(); 150 | $this->type = ((int) $bigint->bitwise_rightShift(52)->toString()) & 0xF; 151 | $this->instance = ((int) $bigint->bitwise_rightShift(32)->toString()) & 0xFFFFF; 152 | $this->accountid = (int) $bigint->bitwise_and(new BigInteger('0xFFFFFFFF', 16))->toString(); 153 | } else { 154 | $this->universe = $id >> 56; 155 | $this->type = ($id >> 52) & 0xF; 156 | $this->instance = ($id >> 32) & 0xFFFFF; 157 | $this->accountid = $id & 0xFFFFFFFF; 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * @return bool True if the SteamID is considered "valid", false otherwise 164 | */ 165 | public function isValid() { 166 | if ($this->type <= self::TYPE_INVALID || $this->type > self::TYPE_ANON_USER) { 167 | return false; 168 | } 169 | 170 | if ($this->universe <= self::UNIVERSE_INVALID || $this->universe > self::UNIVERSE_DEV) { 171 | return false; 172 | } 173 | 174 | if ($this->type == self::TYPE_INDIVIDUAL && ($this->accountid === 0 || $this->instance > self::INSTANCE_WEB)) { 175 | return false; 176 | } 177 | 178 | if ($this->type == self::TYPE_CLAN && ($this->accountid === 0 || $this->instance != self::INSTANCE_ALL)) { 179 | return false; 180 | } 181 | 182 | if ($this->type == self::TYPE_GAMESERVER && $this->accountid === 0) { 183 | return false; 184 | } 185 | 186 | return true; 187 | } 188 | 189 | /** 190 | * Gets the rendered STEAM_X:Y:Z format. 191 | * @param bool $newerFormat If the universe is public, should X be 1 instead of 0? 192 | * @return string 193 | * @throws Exception If this isn't an individual account SteamID 194 | */ 195 | public function getSteam2RenderedID($newerFormat = false) { 196 | if ($this->type != self::TYPE_INDIVIDUAL) { 197 | throw new Exception("Can't get Steam2 rendered ID for non-individual ID"); 198 | } else { 199 | $universe = $this->universe; 200 | if ($universe == 1 && !$newerFormat) { 201 | $universe = 0; 202 | } 203 | 204 | return 'STEAM_' . $universe . ':' . ($this->accountid & 1) . ':' . floor($this->accountid / 2); 205 | } 206 | } 207 | 208 | /** 209 | * Gets the rendered [T:U:A(:I)] format (T = type, U = universe, A = accountid, I = instance) 210 | * @return string 211 | */ 212 | public function getSteam3RenderedID() { 213 | $type_char = self::$typeChars[$this->type] ? self::$typeChars[$this->type] : 'i'; 214 | 215 | if ($this->instance & self::CHAT_INSTANCE_FLAG_CLAN) { 216 | $type_char = 'c'; 217 | } elseif ($this->instance & self::CHAT_INSTANCE_FLAG_LOBBY) { 218 | $type_char = 'L'; 219 | } 220 | 221 | $render_instance = ($this->type == self::TYPE_ANON_GAMESERVER || $this->type == self::TYPE_MULTISEAT || 222 | ($this->type == self::TYPE_INDIVIDUAL && $this->instance != self::INSTANCE_DESKTOP)); 223 | 224 | return '[' . $type_char . ':' . $this->universe . ':' . $this->accountid . ($render_instance ? ':' . $this->instance : '') . ']'; 225 | } 226 | 227 | /** 228 | * Gets the SteamID as a 64-bit integer 229 | * @return string 230 | */ 231 | public function getSteamID64() { 232 | if (PHP_INT_SIZE == 4) { 233 | $ret = new BigInteger(); 234 | $ret = $ret->add((new BigInteger($this->universe))->bitwise_leftShift(56)); 235 | $ret = $ret->add((new BigInteger($this->type))->bitwise_leftShift(52)); 236 | $ret = $ret->add((new BigInteger($this->instance))->bitwise_leftShift(32)); 237 | $ret = $ret->add(new BigInteger($this->accountid)); 238 | return $ret->toString(); 239 | } 240 | return (string) (($this->universe << 56) | ($this->type << 52) | ($this->instance << 32) | ($this->accountid)); 241 | } 242 | 243 | /** 244 | * Gets the SteamID as a 64-bit integer in a string 245 | * @return string 246 | */ 247 | public function __toString() { 248 | return $this->getSteamID64(); 249 | } 250 | 251 | /** 252 | * Returns whether or not this SteamID is for a clan (Steam group) chat 253 | * @return bool 254 | */ 255 | public function isClanChat() { 256 | return $this->type == self::TYPE_CHAT && ($this->instance & self::CHAT_INSTANCE_FLAG_CLAN); 257 | } 258 | 259 | /** 260 | * Returns whether or not this SteamID is for a lobby 261 | * @return bool 262 | */ 263 | public function isLobbyChat() { 264 | return $this->type == self::TYPE_CHAT && ($this->instance & self::CHAT_INSTANCE_FLAG_LOBBY); 265 | } 266 | 267 | /** 268 | * Returns whether or not this SteamID is for a matchmaking lobby 269 | * @return bool 270 | */ 271 | public function isMMSLobbyChat() { 272 | return $this->type == self::TYPE_CHAT && ($this->instance & self::CHAT_INSTANCE_FLAG_MMSLOBBY); 273 | } 274 | 275 | private static function getTypeFromChar($char) { 276 | foreach (self::$typeChars as $type => $typechar) { 277 | if ($typechar == $char) { 278 | return $type; 279 | } 280 | } 281 | 282 | return self::TYPE_INVALID; 283 | } 284 | } 285 | --------------------------------------------------------------------------------