@vlydev/cs2-masked-inspect 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/tests.yml +28 -0
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/index.js +7 -0
- package/package.json +19 -0
- package/src/InspectLink.js +320 -0
- package/src/ItemPreviewData.js +84 -0
- package/src/Sticker.js +48 -0
- package/src/proto/reader.js +128 -0
- package/src/proto/writer.js +137 -0
- package/tests/inspect_link.test.js +361 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a CS2 item as encoded in an inspect link.
|
|
5
|
+
*
|
|
6
|
+
* Fields map directly to the CEconItemPreviewDataBlock protobuf message
|
|
7
|
+
* used by the CS2 game coordinator.
|
|
8
|
+
*
|
|
9
|
+
* paintwear is stored as a float32 (IEEE 754). On the wire it is reinterpreted
|
|
10
|
+
* as a uint32 — this class always exposes it as a JavaScript number for convenience.
|
|
11
|
+
*
|
|
12
|
+
* accountid and itemid may be large integers; they are stored as BigInt internally
|
|
13
|
+
* but exposed as Number when they fit safely (< 2^53).
|
|
14
|
+
*/
|
|
15
|
+
class ItemPreviewData {
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} [opts]
|
|
18
|
+
* @param {number} [opts.accountId=0]
|
|
19
|
+
* @param {number} [opts.itemId=0]
|
|
20
|
+
* @param {number} [opts.defIndex=0]
|
|
21
|
+
* @param {number} [opts.paintIndex=0]
|
|
22
|
+
* @param {number} [opts.rarity=0]
|
|
23
|
+
* @param {number} [opts.quality=0]
|
|
24
|
+
* @param {number} [opts.paintWear=0.0]
|
|
25
|
+
* @param {number} [opts.paintSeed=0]
|
|
26
|
+
* @param {number} [opts.killEaterScoreType=0]
|
|
27
|
+
* @param {number} [opts.killEaterValue=0]
|
|
28
|
+
* @param {string} [opts.customName='']
|
|
29
|
+
* @param {import('./Sticker')[]} [opts.stickers=[]]
|
|
30
|
+
* @param {number} [opts.inventory=0]
|
|
31
|
+
* @param {number} [opts.origin=0]
|
|
32
|
+
* @param {number} [opts.questId=0]
|
|
33
|
+
* @param {number} [opts.dropReason=0]
|
|
34
|
+
* @param {number} [opts.musicIndex=0]
|
|
35
|
+
* @param {number} [opts.entIndex=0]
|
|
36
|
+
* @param {number} [opts.petIndex=0]
|
|
37
|
+
* @param {import('./Sticker')[]} [opts.keychains=[]]
|
|
38
|
+
*/
|
|
39
|
+
constructor({
|
|
40
|
+
accountId = 0,
|
|
41
|
+
itemId = 0,
|
|
42
|
+
defIndex = 0,
|
|
43
|
+
paintIndex = 0,
|
|
44
|
+
rarity = 0,
|
|
45
|
+
quality = 0,
|
|
46
|
+
paintWear = 0.0,
|
|
47
|
+
paintSeed = 0,
|
|
48
|
+
killEaterScoreType = 0,
|
|
49
|
+
killEaterValue = 0,
|
|
50
|
+
customName = '',
|
|
51
|
+
stickers = [],
|
|
52
|
+
inventory = 0,
|
|
53
|
+
origin = 0,
|
|
54
|
+
questId = 0,
|
|
55
|
+
dropReason = 0,
|
|
56
|
+
musicIndex = 0,
|
|
57
|
+
entIndex = 0,
|
|
58
|
+
petIndex = 0,
|
|
59
|
+
keychains = [],
|
|
60
|
+
} = {}) {
|
|
61
|
+
this.accountId = accountId;
|
|
62
|
+
this.itemId = itemId;
|
|
63
|
+
this.defIndex = defIndex;
|
|
64
|
+
this.paintIndex = paintIndex;
|
|
65
|
+
this.rarity = rarity;
|
|
66
|
+
this.quality = quality;
|
|
67
|
+
this.paintWear = paintWear;
|
|
68
|
+
this.paintSeed = paintSeed;
|
|
69
|
+
this.killEaterScoreType = killEaterScoreType;
|
|
70
|
+
this.killEaterValue = killEaterValue;
|
|
71
|
+
this.customName = customName;
|
|
72
|
+
this.stickers = stickers;
|
|
73
|
+
this.inventory = inventory;
|
|
74
|
+
this.origin = origin;
|
|
75
|
+
this.questId = questId;
|
|
76
|
+
this.dropReason = dropReason;
|
|
77
|
+
this.musicIndex = musicIndex;
|
|
78
|
+
this.entIndex = entIndex;
|
|
79
|
+
this.petIndex = petIndex;
|
|
80
|
+
this.keychains = keychains;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = ItemPreviewData;
|
package/src/Sticker.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a sticker or keychain applied to a CS2 item.
|
|
5
|
+
*
|
|
6
|
+
* Maps to the Sticker protobuf message nested inside CEconItemPreviewDataBlock.
|
|
7
|
+
* The same message is used for both stickers (field 12) and keychains (field 20).
|
|
8
|
+
*/
|
|
9
|
+
class Sticker {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} [opts]
|
|
12
|
+
* @param {number} [opts.slot=0]
|
|
13
|
+
* @param {number} [opts.stickerId=0]
|
|
14
|
+
* @param {number|null} [opts.wear=null]
|
|
15
|
+
* @param {number|null} [opts.scale=null]
|
|
16
|
+
* @param {number|null} [opts.rotation=null]
|
|
17
|
+
* @param {number} [opts.tintId=0]
|
|
18
|
+
* @param {number|null} [opts.offsetX=null]
|
|
19
|
+
* @param {number|null} [opts.offsetY=null]
|
|
20
|
+
* @param {number|null} [opts.offsetZ=null]
|
|
21
|
+
* @param {number} [opts.pattern=0]
|
|
22
|
+
*/
|
|
23
|
+
constructor({
|
|
24
|
+
slot = 0,
|
|
25
|
+
stickerId = 0,
|
|
26
|
+
wear = null,
|
|
27
|
+
scale = null,
|
|
28
|
+
rotation = null,
|
|
29
|
+
tintId = 0,
|
|
30
|
+
offsetX = null,
|
|
31
|
+
offsetY = null,
|
|
32
|
+
offsetZ = null,
|
|
33
|
+
pattern = 0,
|
|
34
|
+
} = {}) {
|
|
35
|
+
this.slot = slot;
|
|
36
|
+
this.stickerId = stickerId;
|
|
37
|
+
this.wear = wear;
|
|
38
|
+
this.scale = scale;
|
|
39
|
+
this.rotation = rotation;
|
|
40
|
+
this.tintId = tintId;
|
|
41
|
+
this.offsetX = offsetX;
|
|
42
|
+
this.offsetY = offsetY;
|
|
43
|
+
this.offsetZ = offsetZ;
|
|
44
|
+
this.pattern = pattern;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Sticker;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure JavaScript protobuf binary reader.
|
|
5
|
+
*
|
|
6
|
+
* Implements the subset of wire types needed for CEconItemPreviewDataBlock:
|
|
7
|
+
* - Wire type 0: varint (uint32, uint64, int32)
|
|
8
|
+
* - Wire type 2: length-delimited (string, bytes, nested messages)
|
|
9
|
+
* - Wire type 5: 32-bit fixed (float32)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const WIRE_VARINT = 0;
|
|
13
|
+
const WIRE_64BIT = 1;
|
|
14
|
+
const WIRE_LEN = 2;
|
|
15
|
+
const WIRE_32BIT = 5;
|
|
16
|
+
|
|
17
|
+
class ProtoReader {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Buffer} data
|
|
20
|
+
*/
|
|
21
|
+
constructor(data) {
|
|
22
|
+
this._data = data;
|
|
23
|
+
this._pos = 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get pos() {
|
|
27
|
+
return this._pos;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
remaining() {
|
|
31
|
+
return this._data.length - this._pos;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
readByte() {
|
|
35
|
+
if (this._pos >= this._data.length) {
|
|
36
|
+
throw new RangeError('Unexpected end of protobuf data');
|
|
37
|
+
}
|
|
38
|
+
return this._data[this._pos++];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
readBytes(n) {
|
|
42
|
+
if (this._pos + n > this._data.length) {
|
|
43
|
+
throw new RangeError(
|
|
44
|
+
`Need ${n} bytes but only ${this._data.length - this._pos} remain`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const chunk = this._data.slice(this._pos, this._pos + n);
|
|
48
|
+
this._pos += n;
|
|
49
|
+
return chunk;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read a base-128 varint.
|
|
54
|
+
* Returns a BigInt for 64-bit range safety; callers convert as needed.
|
|
55
|
+
*
|
|
56
|
+
* @returns {bigint}
|
|
57
|
+
*/
|
|
58
|
+
readVarint() {
|
|
59
|
+
let result = 0n;
|
|
60
|
+
let shift = 0n;
|
|
61
|
+
|
|
62
|
+
while (true) {
|
|
63
|
+
const b = this.readByte();
|
|
64
|
+
result |= BigInt(b & 0x7F) << shift;
|
|
65
|
+
if (!(b & 0x80)) break;
|
|
66
|
+
shift += 7n;
|
|
67
|
+
if (shift > 63n) {
|
|
68
|
+
throw new RangeError('Varint too long');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Read tag and return [fieldNumber, wireType].
|
|
77
|
+
*
|
|
78
|
+
* @returns {[number, number]}
|
|
79
|
+
*/
|
|
80
|
+
readTag() {
|
|
81
|
+
const tag = this.readVarint();
|
|
82
|
+
return [Number(tag >> 3n), Number(tag & 7n)];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
readLengthDelimited() {
|
|
86
|
+
const length = Number(this.readVarint());
|
|
87
|
+
return this.readBytes(length);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read all fields until EOF.
|
|
92
|
+
*
|
|
93
|
+
* @returns {Array<{field: number, wire: number, value: bigint|Buffer}>}
|
|
94
|
+
*/
|
|
95
|
+
readAllFields() {
|
|
96
|
+
const fields = [];
|
|
97
|
+
|
|
98
|
+
while (this.remaining() > 0) {
|
|
99
|
+
const [fieldNum, wireType] = this.readTag();
|
|
100
|
+
|
|
101
|
+
let value;
|
|
102
|
+
switch (wireType) {
|
|
103
|
+
case WIRE_VARINT:
|
|
104
|
+
value = this.readVarint();
|
|
105
|
+
break;
|
|
106
|
+
case WIRE_64BIT:
|
|
107
|
+
value = this.readBytes(8);
|
|
108
|
+
break;
|
|
109
|
+
case WIRE_LEN:
|
|
110
|
+
value = this.readLengthDelimited();
|
|
111
|
+
break;
|
|
112
|
+
case WIRE_32BIT:
|
|
113
|
+
value = this.readBytes(4);
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
throw new RangeError(
|
|
117
|
+
`Unknown wire type ${wireType} for field ${fieldNum}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fields.push({ field: fieldNum, wire: wireType, value });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return fields;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { ProtoReader, WIRE_VARINT, WIRE_64BIT, WIRE_LEN, WIRE_32BIT };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure JavaScript protobuf binary writer.
|
|
5
|
+
*
|
|
6
|
+
* Writes to an in-memory buffer; call toBytes() to retrieve the result.
|
|
7
|
+
* Fields with default/zero values are omitted (proto3 semantics).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const WIRE_VARINT = 0;
|
|
11
|
+
const WIRE_LEN = 2;
|
|
12
|
+
const WIRE_32BIT = 5;
|
|
13
|
+
|
|
14
|
+
class ProtoWriter {
|
|
15
|
+
constructor() {
|
|
16
|
+
/** @type {Buffer[]} */
|
|
17
|
+
this._buf = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toBytes() {
|
|
21
|
+
return Buffer.concat(this._buf);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ------------------------------------------------------------------
|
|
25
|
+
// Low-level primitives
|
|
26
|
+
// ------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {bigint|number} value
|
|
30
|
+
*/
|
|
31
|
+
_writeVarint(value) {
|
|
32
|
+
let v = BigInt(value);
|
|
33
|
+
// Handle negative: treat as unsigned 64-bit two's complement
|
|
34
|
+
if (v < 0n) {
|
|
35
|
+
v = BigInt.asUintN(64, v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parts = [];
|
|
39
|
+
do {
|
|
40
|
+
let b = Number(v & 0x7Fn);
|
|
41
|
+
v >>= 7n;
|
|
42
|
+
if (v !== 0n) b |= 0x80;
|
|
43
|
+
parts.push(b);
|
|
44
|
+
} while (v !== 0n);
|
|
45
|
+
|
|
46
|
+
this._buf.push(Buffer.from(parts));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_writeTag(fieldNum, wireType) {
|
|
50
|
+
this._writeVarint((fieldNum << 3) | wireType);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ------------------------------------------------------------------
|
|
54
|
+
// Public field writers
|
|
55
|
+
// ------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {number} fieldNum
|
|
59
|
+
* @param {number|bigint} value
|
|
60
|
+
*/
|
|
61
|
+
writeUint32(fieldNum, value) {
|
|
62
|
+
if (value === 0 || value === 0n) return;
|
|
63
|
+
this._writeTag(fieldNum, WIRE_VARINT);
|
|
64
|
+
this._writeVarint(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {number} fieldNum
|
|
69
|
+
* @param {number|bigint} value
|
|
70
|
+
*/
|
|
71
|
+
writeUint64(fieldNum, value) {
|
|
72
|
+
if (value === 0 || value === 0n) return;
|
|
73
|
+
this._writeTag(fieldNum, WIRE_VARINT);
|
|
74
|
+
this._writeVarint(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {number} fieldNum
|
|
79
|
+
* @param {number|bigint} value
|
|
80
|
+
*/
|
|
81
|
+
writeInt32(fieldNum, value) {
|
|
82
|
+
if (value === 0 || value === 0n) return;
|
|
83
|
+
this._writeTag(fieldNum, WIRE_VARINT);
|
|
84
|
+
this._writeVarint(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} fieldNum
|
|
89
|
+
* @param {string} value
|
|
90
|
+
*/
|
|
91
|
+
writeString(fieldNum, value) {
|
|
92
|
+
if (!value) return;
|
|
93
|
+
const encoded = Buffer.from(value, 'utf8');
|
|
94
|
+
this._writeTag(fieldNum, WIRE_LEN);
|
|
95
|
+
this._writeVarint(encoded.length);
|
|
96
|
+
this._buf.push(encoded);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Write a float32 as wire type 5 (fixed 32-bit, little-endian).
|
|
101
|
+
* Used for sticker float fields (wear, scale, rotation, etc.).
|
|
102
|
+
*
|
|
103
|
+
* @param {number} fieldNum
|
|
104
|
+
* @param {number} value
|
|
105
|
+
*/
|
|
106
|
+
writeFloat32Fixed(fieldNum, value) {
|
|
107
|
+
this._writeTag(fieldNum, WIRE_32BIT);
|
|
108
|
+
const b = Buffer.alloc(4);
|
|
109
|
+
b.writeFloatLE(value, 0);
|
|
110
|
+
this._buf.push(b);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Write raw bytes as a length-delimited field (wire type 2).
|
|
115
|
+
*
|
|
116
|
+
* @param {number} fieldNum
|
|
117
|
+
* @param {Buffer} data
|
|
118
|
+
*/
|
|
119
|
+
writeRawBytes(fieldNum, data) {
|
|
120
|
+
if (!data || data.length === 0) return;
|
|
121
|
+
this._writeTag(fieldNum, WIRE_LEN);
|
|
122
|
+
this._writeVarint(data.length);
|
|
123
|
+
this._buf.push(data);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Write a nested message (another ProtoWriter's output) as a length-delimited field.
|
|
128
|
+
*
|
|
129
|
+
* @param {number} fieldNum
|
|
130
|
+
* @param {ProtoWriter} nested
|
|
131
|
+
*/
|
|
132
|
+
writeEmbedded(fieldNum, nested) {
|
|
133
|
+
this.writeRawBytes(fieldNum, nested.toBytes());
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { ProtoWriter, WIRE_VARINT, WIRE_LEN, WIRE_32BIT };
|