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 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
@@ -32,7 +32,7 @@
32
32
  "require": "./dist/index.cjs"
33
33
  },
34
34
  "author": "GameDig Contributors",
35
- "version": "0.3.4",
35
+ "version": "0.3.5",
36
36
  "repository": {
37
37
  "type": "git",
38
38
  "url": "https://github.com/gamedig/node-gamedig.git"
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)