gamedigz 0.3.4 → 0.3.5
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/dist/index.cjs +63 -2
- package/package.json +1 -1
- package/protocols/dayz.js +70 -2
package/dist/index.cjs
CHANGED
|
@@ -17453,6 +17453,26 @@ var beammp = class extends Core {
|
|
|
17453
17453
|
|
|
17454
17454
|
// protocols/dayz.js
|
|
17455
17455
|
var import_node_buffer6 = require("node:buffer");
|
|
17456
|
+
function dayzTitleHasControlGarbage(s) {
|
|
17457
|
+
for (let i = 0; i < s.length; i++) {
|
|
17458
|
+
const c = s.charCodeAt(i);
|
|
17459
|
+
if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
|
|
17460
|
+
return true;
|
|
17461
|
+
}
|
|
17462
|
+
}
|
|
17463
|
+
return false;
|
|
17464
|
+
}
|
|
17465
|
+
function dayzModTitleLooksLikeGameOrNameLeak(title, state) {
|
|
17466
|
+
var _a;
|
|
17467
|
+
const game = ((_a = state == null ? void 0 : state.raw) == null ? void 0 : _a.game) != null && String(state.raw.game).trim() || "";
|
|
17468
|
+
const name = (state == null ? void 0 : state.name) != null && String(state.name).trim() || "";
|
|
17469
|
+
for (const ref of [game, name]) {
|
|
17470
|
+
if (ref.length >= 2 && title === ref.slice(1)) {
|
|
17471
|
+
return true;
|
|
17472
|
+
}
|
|
17473
|
+
}
|
|
17474
|
+
return false;
|
|
17475
|
+
}
|
|
17456
17476
|
var dayz = class extends valve {
|
|
17457
17477
|
async run(state) {
|
|
17458
17478
|
if (!this.options.port)
|
|
@@ -17499,7 +17519,7 @@ var dayz = class extends valve {
|
|
|
17499
17519
|
const key = reader.string();
|
|
17500
17520
|
rules[key] = reader.string();
|
|
17501
17521
|
}
|
|
17502
|
-
state.raw.dayzMods = this.readDayzMods(import_node_buffer6.Buffer.from(dayZPayload));
|
|
17522
|
+
state.raw.dayzMods = this.sanitizeDayzModsList(this.readDayzMods(import_node_buffer6.Buffer.from(dayZPayload)), state);
|
|
17503
17523
|
}
|
|
17504
17524
|
processQueryInfo(state) {
|
|
17505
17525
|
if (!state.raw.tags) {
|
|
@@ -17551,7 +17571,7 @@ var dayz = class extends valve {
|
|
|
17551
17571
|
}
|
|
17552
17572
|
readDayzMods(buffer) {
|
|
17553
17573
|
if (!buffer.length) {
|
|
17554
|
-
return
|
|
17574
|
+
return [];
|
|
17555
17575
|
}
|
|
17556
17576
|
this.logger.debug("DAYZ BUFFER");
|
|
17557
17577
|
this.logger.debug(buffer);
|
|
@@ -17578,6 +17598,47 @@ var dayz = class extends valve {
|
|
|
17578
17598
|
this.logger.debug("dayz buffer rest:", reader.rest());
|
|
17579
17599
|
return mods;
|
|
17580
17600
|
}
|
|
17601
|
+
/**
|
|
17602
|
+
* Drop entries that are clearly mis-parsed (binary in title, empty names) or
|
|
17603
|
+
* non-objects. Steam Workshop titles are never empty for listed items; empty
|
|
17604
|
+
* titles usually mean padding or misalignment after the real mod list.
|
|
17605
|
+
* Also drops rows where the title is the server "game" or hostname string
|
|
17606
|
+
* with the first character missing (rules blob misaligned with A2S strings).
|
|
17607
|
+
*/
|
|
17608
|
+
sanitizeDayzModsList(mods, state) {
|
|
17609
|
+
if (!Array.isArray(mods)) {
|
|
17610
|
+
return [];
|
|
17611
|
+
}
|
|
17612
|
+
return mods.filter((mod) => {
|
|
17613
|
+
if (mod == null || typeof mod !== "object") {
|
|
17614
|
+
return false;
|
|
17615
|
+
}
|
|
17616
|
+
const rawTitle = mod.title;
|
|
17617
|
+
if (typeof rawTitle !== "string") {
|
|
17618
|
+
return false;
|
|
17619
|
+
}
|
|
17620
|
+
const title = rawTitle.trim();
|
|
17621
|
+
if (!title) {
|
|
17622
|
+
return false;
|
|
17623
|
+
}
|
|
17624
|
+
if (state != null && dayzModTitleLooksLikeGameOrNameLeak(title, state)) {
|
|
17625
|
+
return false;
|
|
17626
|
+
}
|
|
17627
|
+
if (dayzTitleHasControlGarbage(rawTitle)) {
|
|
17628
|
+
return false;
|
|
17629
|
+
}
|
|
17630
|
+
if ("workshopId" in mod) {
|
|
17631
|
+
const id = mod.workshopId;
|
|
17632
|
+
if (typeof id !== "number" || !Number.isFinite(id) || id <= 0) {
|
|
17633
|
+
return false;
|
|
17634
|
+
}
|
|
17635
|
+
}
|
|
17636
|
+
return true;
|
|
17637
|
+
}).map((mod) => {
|
|
17638
|
+
const title = typeof mod.title === "string" ? mod.title.trim() : mod.title;
|
|
17639
|
+
return { ...mod, title };
|
|
17640
|
+
});
|
|
17641
|
+
}
|
|
17581
17642
|
readDayzModsSection(reader, withHeader) {
|
|
17582
17643
|
const out = [];
|
|
17583
17644
|
const count = reader.uint(1);
|
package/package.json
CHANGED
package/protocols/dayz.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import valve from './valve.js'
|
|
2
2
|
import { Buffer } from 'node:buffer'
|
|
3
3
|
|
|
4
|
+
/** C0 control / non-printable garbage (misaligned binary parsed as title). */
|
|
5
|
+
function dayzTitleHasControlGarbage (s) {
|
|
6
|
+
for (let i = 0; i < s.length; i++) {
|
|
7
|
+
const c = s.charCodeAt(i)
|
|
8
|
+
if (c < 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d) {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Some servers with no (or empty) mod payload mis-parse the A2S "game" / hostname
|
|
17
|
+
* string into the mod list with a 1-code-unit offset (missing first character).
|
|
18
|
+
*/
|
|
19
|
+
function dayzModTitleLooksLikeGameOrNameLeak (title, state) {
|
|
20
|
+
const game = (state?.raw?.game != null && String(state.raw.game).trim()) || ''
|
|
21
|
+
const name = (state?.name != null && String(state.name).trim()) || ''
|
|
22
|
+
for (const ref of [game, name]) {
|
|
23
|
+
if (ref.length >= 2 && title === ref.slice(1)) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
4
30
|
export default class dayz extends valve {
|
|
5
31
|
async run (state) {
|
|
6
32
|
if (!this.options.port) this.options.port = 27016
|
|
@@ -53,7 +79,7 @@ export default class dayz extends valve {
|
|
|
53
79
|
rules[key] = reader.string()
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
state.raw.dayzMods = this.readDayzMods(Buffer.from(dayZPayload))
|
|
82
|
+
state.raw.dayzMods = this.sanitizeDayzModsList(this.readDayzMods(Buffer.from(dayZPayload)), state)
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
processQueryInfo (state) {
|
|
@@ -109,7 +135,7 @@ export default class dayz extends valve {
|
|
|
109
135
|
|
|
110
136
|
readDayzMods (/** Buffer */ buffer) {
|
|
111
137
|
if (!buffer.length) {
|
|
112
|
-
return
|
|
138
|
+
return []
|
|
113
139
|
}
|
|
114
140
|
|
|
115
141
|
this.logger.debug('DAYZ BUFFER')
|
|
@@ -142,6 +168,48 @@ export default class dayz extends valve {
|
|
|
142
168
|
return mods
|
|
143
169
|
}
|
|
144
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Drop entries that are clearly mis-parsed (binary in title, empty names) or
|
|
173
|
+
* non-objects. Steam Workshop titles are never empty for listed items; empty
|
|
174
|
+
* titles usually mean padding or misalignment after the real mod list.
|
|
175
|
+
* Also drops rows where the title is the server "game" or hostname string
|
|
176
|
+
* with the first character missing (rules blob misaligned with A2S strings).
|
|
177
|
+
*/
|
|
178
|
+
sanitizeDayzModsList (mods, state) {
|
|
179
|
+
if (!Array.isArray(mods)) {
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
return mods.filter((mod) => {
|
|
183
|
+
if (mod == null || typeof mod !== 'object') {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
const rawTitle = mod.title
|
|
187
|
+
if (typeof rawTitle !== 'string') {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
const title = rawTitle.trim()
|
|
191
|
+
if (!title) {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
if (state != null && dayzModTitleLooksLikeGameOrNameLeak(title, state)) {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
if (dayzTitleHasControlGarbage(rawTitle)) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
if ('workshopId' in mod) {
|
|
201
|
+
const id = mod.workshopId
|
|
202
|
+
if (typeof id !== 'number' || !Number.isFinite(id) || id <= 0) {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return true
|
|
207
|
+
}).map((mod) => {
|
|
208
|
+
const title = typeof mod.title === 'string' ? mod.title.trim() : mod.title
|
|
209
|
+
return { ...mod, title }
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
145
213
|
readDayzModsSection (/** Reader */ reader, withHeader) {
|
|
146
214
|
const out = []
|
|
147
215
|
// Entry count is a raw byte; readDayzByte can mis-handle value 1 (0x01)
|