Pure PHP 8.2+ library for encoding and decoding CS2 masked inspect links — no runtime dependencies.
composer require vlydev/cs2-masked-inspectuse VlyDev\Steam\InspectLink;
// Accepts a full steam:// URL or a raw hex string
$item = InspectLink::deserialize(
'steam://run/730//+csgo_econ_action_preview%20E3F3367440334DE2FBE4C345E0CBE0D3...'
);
echo $item->defindex; // 7 (AK-47)
echo $item->paintindex; // 422
echo $item->paintseed; // 922
echo $item->paintwear; // ~0.04121
echo $item->itemid; // 46876117973
foreach ($item->stickers as $s) {
echo $s->stickerId; // 7436, 5144, 6970, 8069, 5592
}use VlyDev\Steam\InspectLink;
use VlyDev\Steam\ItemPreviewData;
$data = new ItemPreviewData(
defindex: 60,
paintindex: 440,
paintseed: 353,
paintwear: 0.005411375779658556,
rarity: 5,
);
$hex = InspectLink::serialize($data);
// 00183C20B803280538E9A3C5DD0340E102C246A0D1
$url = "steam://run/730//+csgo_econ_action_preview%20{$hex}";use VlyDev\Steam\InspectLink;
use VlyDev\Steam\ItemPreviewData;
use VlyDev\Steam\Sticker;
$data = new ItemPreviewData(
defindex: 7,
paintindex: 422,
paintseed: 922,
paintwear: 0.04121,
rarity: 3,
quality: 4,
stickers: [
new Sticker(slot: 0, stickerId: 7436),
new Sticker(slot: 1, stickerId: 5144, wear: 0.1),
],
);
$hex = InspectLink::serialize($data);
$decoded = InspectLink::deserialize($hex); // round-tripUse isMasked() and isClassic() to detect the link type without attempting to decode it.
use VlyDev\Steam\InspectLink;
// New masked format (pure hex blob) — can be decoded offline
$maskedUrl = 'steam://run/730//+csgo_econ_action_preview%20E3F3...';
InspectLink::isMasked($maskedUrl); // true
InspectLink::isClassic($maskedUrl); // false
// Hybrid format (S/A/D prefix with hex proto after D) — also decodable offline
$hybridUrl = 'steam://rungame/730/.../+csgo_econ_action_preview%20S76561199323320483A50075495125D1101C4C4FCD4AB10...';
InspectLink::isMasked($hybridUrl); // true
InspectLink::isClassic($hybridUrl); // false
// Classic format — requires Steam Game Coordinator to fetch item info
$classicUrl = 'steam://rungame/730/.../+csgo_econ_action_preview%20S76561199842063946A49749521570D2751293026650298712';
InspectLink::isMasked($classicUrl); // false
InspectLink::isClassic($classicUrl); // truedeserialize() enforces:
| Rule | Limit | Exception |
|---|---|---|
| Hex payload length | max 4,096 characters | InvalidArgumentException |
| Protobuf field count | max 100 per message | OverflowException |
serialize() enforces:
| Field | Constraint | Exception |
|---|---|---|
paintwear |
[0.0, 1.0] |
InvalidArgumentException |
customname |
max 100 characters | InvalidArgumentException |
Three URL formats are handled:
-
New masked format — pure hex blob after
csgo_econ_action_preview:steam://run/730//+csgo_econ_action_preview%20<hexbytes> -
Hybrid format — old-style
S/A/Dprefix, but with a hex proto appended afterD(instead of a decimal did):steam://rungame/730/.../+csgo_econ_action_preview%20S<steamid>A<assetid>D<hexproto> -
Classic format — old-style
S/A/Dwith a decimal did; requires Steam GC to resolve item details.
For formats 1 and 2 the library decodes the item offline. For format 3 only URL parsing is possible.
The hex blob (formats 1 and 2) has the following binary layout:
[key_byte] [proto_bytes XOR'd with key] [4-byte checksum XOR'd with key]
| Section | Size | Description |
|---|---|---|
key_byte |
1 byte | XOR key. 0x00 = no obfuscation (tool links). Other values = native CS2 links. |
proto_bytes |
variable | CEconItemPreviewDataBlock protobuf, each byte XOR'd with key_byte. |
checksum |
4 bytes | Big-endian uint32, XOR'd with key_byte. |
$buffer = "\x00" . $protoBytes;
$crc = crc32($buffer) & 0xFFFFFFFF;
$xored = (($crc & 0xFFFF) ^ (strlen($protoBytes) * $crc)) & 0xFFFFFFFF;
$checksum = pack('N', $xored); // big-endian uint32paintwear is stored as a uint32 varint whose bit pattern is the IEEE 754 representation
of a float32. The library handles this transparently — callers always work with PHP float values.
| Field | Number | Type | Description |
|---|---|---|---|
accountid |
1 | uint32 | Steam account ID (often 0) |
itemid |
2 | uint64 | Item ID in the owner's inventory |
defindex |
3 | uint32 | Item definition index (weapon type) |
paintindex |
4 | uint32 | Skin paint index |
rarity |
5 | uint32 | Item rarity |
quality |
6 | uint32 | Item quality |
paintwear |
7 | uint32* | float32 reinterpreted as uint32 |
paintseed |
8 | uint32 | Pattern seed (0–1000) |
killeaterscoretype |
9 | uint32 | StatTrak counter type |
killeatervalue |
10 | uint32 | StatTrak value |
customname |
11 | string | Name tag |
stickers |
12 | repeated Sticker | Applied stickers |
inventory |
13 | uint32 | Inventory flags |
origin |
14 | uint32 | Origin |
questid |
15 | uint32 | Quest ID |
dropreason |
16 | uint32 | Drop reason |
musicindex |
17 | uint32 | Music kit index |
entindex |
18 | int32 | Entity index |
petindex |
19 | uint32 | Pet index |
keychains |
20 | repeated Sticker | Applied keychains |
| Field | Number | Type | Description |
|---|---|---|---|
slot |
1 | uint32 | Slot position |
sticker_id |
2 | uint32 | Sticker definition ID |
wear |
3 | float32 | Wear (fixed32) |
scale |
4 | float32 | Scale (fixed32) |
rotation |
5 | float32 | Rotation (fixed32) |
tint_id |
6 | uint32 | Tint |
offset_x |
7 | float32 | X offset (fixed32) |
offset_y |
8 | float32 | Y offset (fixed32) |
offset_z |
9 | float32 | Z offset (fixed32) |
pattern |
10 | uint32 | Pattern (keychains) |
E3F3367440334DE2FBE4C345E0CBE0D3E7DB6943400AE0A379E481ECEBE2F36F
D9DE2BDB515EA6E30D74D981ECEBE3F37BCBDE640D475DA6E35EFCD881ECEBE3
F359D5DE37E9D75DA6436DD3DD81ECEBE3F366DCDE3F8F9BDDA69B43B6DE81EC
EBE3F33BC8DEBB1CA3DFA623F7DDDF8B71E293EBFD43382B
| Field | Value |
|---|---|
itemid |
46876117973 |
defindex |
7 (AK-47) |
paintindex |
422 |
paintseed |
922 |
paintwear |
≈ 0.04121 |
rarity |
3 |
quality |
4 |
| sticker IDs | [7436, 5144, 6970, 8069, 5592] |
new ItemPreviewData(defindex: 60, paintindex: 440, paintseed: 353,
paintwear: 0.005411375779658556, rarity: 5)Expected hex:
00183C20B803280538E9A3C5DD0340E102C246A0D1
composer install
composer testBug reports and pull requests are welcome on GitHub.
- Fork the repository
- Create a branch:
git checkout -b my-fix - Make your changes and add tests
- Ensure all tests pass:
composer test - Open a Pull Request
All PRs require the CI checks to pass before merging.
VlyDev — vladdnepr1989@gmail.com
MIT © VlyDev