t2-demo-parser 2.2.0 → 3.0.1

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.
Files changed (95) hide show
  1. package/README.md +170 -7
  2. package/dist/BitStream.d.ts +14 -3
  3. package/dist/BitStream.d.ts.map +1 -1
  4. package/dist/BitStream.js +28 -6
  5. package/dist/BitStream.js.map +1 -1
  6. package/dist/BitWriter.d.ts +29 -0
  7. package/dist/BitWriter.d.ts.map +1 -0
  8. package/dist/BitWriter.js +76 -0
  9. package/dist/BitWriter.js.map +1 -0
  10. package/dist/ClassRegistry.d.ts +23 -28
  11. package/dist/ClassRegistry.d.ts.map +1 -1
  12. package/dist/ClassRegistry.js +75 -84
  13. package/dist/ClassRegistry.js.map +1 -1
  14. package/dist/DataBlockParsers.d.ts.map +1 -1
  15. package/dist/DataBlockParsers.js +60 -85
  16. package/dist/DataBlockParsers.js.map +1 -1
  17. package/dist/DemoParser.d.ts +23 -22
  18. package/dist/DemoParser.d.ts.map +1 -1
  19. package/dist/DemoParser.js +218 -266
  20. package/dist/DemoParser.js.map +1 -1
  21. package/dist/EventParsers.d.ts.map +1 -1
  22. package/dist/EventParsers.js +87 -58
  23. package/dist/EventParsers.js.map +1 -1
  24. package/dist/GhostManager.d.ts +7 -1
  25. package/dist/GhostManager.d.ts.map +1 -1
  26. package/dist/GhostManager.js +231 -146
  27. package/dist/GhostManager.js.map +1 -1
  28. package/dist/HuffmanProcessor.d.ts +15 -0
  29. package/dist/HuffmanProcessor.d.ts.map +1 -1
  30. package/dist/HuffmanProcessor.js +45 -0
  31. package/dist/HuffmanProcessor.js.map +1 -1
  32. package/dist/LiveParser.d.ts +20 -1
  33. package/dist/LiveParser.d.ts.map +1 -1
  34. package/dist/LiveParser.js +31 -12
  35. package/dist/LiveParser.js.map +1 -1
  36. package/dist/PacketParser.d.ts +22 -2
  37. package/dist/PacketParser.d.ts.map +1 -1
  38. package/dist/PacketParser.js +111 -48
  39. package/dist/PacketParser.js.map +1 -1
  40. package/dist/Timeline.d.ts +5 -2
  41. package/dist/Timeline.d.ts.map +1 -1
  42. package/dist/Timeline.js +25 -12
  43. package/dist/Timeline.js.map +1 -1
  44. package/dist/cli.js +3 -0
  45. package/dist/cli.js.map +1 -1
  46. package/dist/dataBlockDataTypes.d.ts +118 -117
  47. package/dist/dataBlockDataTypes.d.ts.map +1 -1
  48. package/dist/dataBlockDataTypes.js.map +1 -1
  49. package/dist/defaultRegistry.d.ts +9 -0
  50. package/dist/defaultRegistry.d.ts.map +1 -0
  51. package/dist/defaultRegistry.js +43 -0
  52. package/dist/defaultRegistry.js.map +1 -0
  53. package/dist/eventDataTypes.d.ts +74 -72
  54. package/dist/eventDataTypes.d.ts.map +1 -1
  55. package/dist/eventDataTypes.js.map +1 -1
  56. package/dist/ghostDataTypes.d.ts +125 -131
  57. package/dist/ghostDataTypes.d.ts.map +1 -1
  58. package/dist/ghostDataTypes.js.map +1 -1
  59. package/dist/index.d.ts +6 -4
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +4 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/types.d.ts +51 -2
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +6 -0
  66. package/dist/types.js.map +1 -1
  67. package/package.json +4 -1
  68. package/dist/BitStream.test.d.ts +0 -2
  69. package/dist/BitStream.test.d.ts.map +0 -1
  70. package/dist/BitStream.test.js +0 -419
  71. package/dist/BitStream.test.js.map +0 -1
  72. package/dist/ClassRegistry.test.d.ts +0 -2
  73. package/dist/ClassRegistry.test.d.ts.map +0 -1
  74. package/dist/ClassRegistry.test.js +0 -83
  75. package/dist/ClassRegistry.test.js.map +0 -1
  76. package/dist/DataBlockParsers.test.d.ts +0 -2
  77. package/dist/DataBlockParsers.test.d.ts.map +0 -1
  78. package/dist/DataBlockParsers.test.js +0 -294
  79. package/dist/DataBlockParsers.test.js.map +0 -1
  80. package/dist/DemoParser.test.d.ts +0 -2
  81. package/dist/DemoParser.test.d.ts.map +0 -1
  82. package/dist/DemoParser.test.js +0 -472
  83. package/dist/DemoParser.test.js.map +0 -1
  84. package/dist/GhostStateAccumulator.test.d.ts +0 -2
  85. package/dist/GhostStateAccumulator.test.d.ts.map +0 -1
  86. package/dist/GhostStateAccumulator.test.js +0 -201
  87. package/dist/GhostStateAccumulator.test.js.map +0 -1
  88. package/dist/GhostTracker.test.d.ts +0 -2
  89. package/dist/GhostTracker.test.d.ts.map +0 -1
  90. package/dist/GhostTracker.test.js +0 -64
  91. package/dist/GhostTracker.test.js.map +0 -1
  92. package/dist/IncrementalParser.test.d.ts +0 -2
  93. package/dist/IncrementalParser.test.d.ts.map +0 -1
  94. package/dist/IncrementalParser.test.js +0 -226
  95. package/dist/IncrementalParser.test.js.map +0 -1
@@ -7,6 +7,8 @@
7
7
  * getBitCount(getNextPow2(0x800)) = 11 bits.
8
8
  * If flag is false, sets objectId to -1 (0xFFFFFFFF).
9
9
  */
10
+ /** particleEngine.cc: PDC_MAX_TEX. */
11
+ const ParticleDataMaxTextures = 50;
10
12
  function readDataBlockRef(bs) {
11
13
  return bs.readFlag() ? bs.readInt(11) : null;
12
14
  }
@@ -22,6 +24,11 @@ function readRangedS32(bs, min, max) {
22
24
  * From decompiled binary: FUN_0043f040 calls FUN_0043efe0 which reads 4 × stream->read(1, &byte).
23
25
  * Each byte is multiplied by 1/255.0 to produce the float value.
24
26
  * NOTE: This is NOT 4×F32 (128 bits) — it's 4×U8 (32 bits).
27
+ *
28
+ * Colour representation follows the engine's own field type: fields the
29
+ * engine stores as ColorF (this reader) are normalised to [0, 1]; fields
30
+ * it stores as ColorI (sensorColor, sensor group colours) keep the raw
31
+ * 0–255 bytes.
25
32
  */
26
33
  function readColorF(bs) {
27
34
  return {
@@ -37,13 +44,23 @@ function readColorF(bs) {
37
44
  * Many DataBlock fields use write(sizeof(bool)) instead of writeFlag().
38
45
  */
39
46
  function readBool(bs) {
40
- return bs.readInt(8) !== 0;
47
+ return bs.readBool();
48
+ }
49
+ /** Read a ranged float: min + readFloat(bits) * (max - min). */
50
+ function readRangedF32(bs, min, max, bits) {
51
+ return min + bs.readFloat(bits) * (max - min);
41
52
  }
42
53
  /**
43
- * Read a ranged float: min + readInt(bits) / ((1 << bits) - 1) * (max - min).
54
+ * Read a U32 count and reject it unless `minBitsPerEntry × count` bits
55
+ * remain: exhausted reads return 0 without throwing, so a corrupt count
56
+ * would otherwise spin allocating until memory ran out.
44
57
  */
45
- function readRangedF32(bs, min, max, bits) {
46
- return min + (bs.readInt(bits) / ((1 << bits) - 1)) * (max - min);
58
+ function readCheckedCount(bs, minBitsPerEntry, what) {
59
+ const count = bs.readU32();
60
+ if (count > bs.getRemainingBits() / minBitsPerEntry) {
61
+ throw new Error(`Invalid ${what}: ${count}`);
62
+ }
63
+ return count;
47
64
  }
48
65
  // ============================================================
49
66
  // Base class parsers (inheritance chain)
@@ -90,6 +107,7 @@ function shapeBaseDataUnpack(bs) {
90
107
  result.debrisShapeName = bs.readString();
91
108
  // sensorRadius — flag; if true: readInt(10) + 4×U8 (RGBA bytes)
92
109
  // Binary: readInt(10) → this+0x15c (cast to float), then 4 × read(1) → 0x160-0x163
110
+ // sensorColor is a ColorI in the engine, so the bytes are kept as-is.
93
111
  if (bs.readFlag()) {
94
112
  result.sensorRadius = bs.readInt(10);
95
113
  result.sensorColor = {
@@ -134,11 +152,7 @@ function shapeBaseDataUnpack(bs) {
134
152
  // Binary: readFlag, if true: read(4) × 3 → this+0x98, 0x9c, 0xa0
135
153
  // NOTE: This field is NOT in the V12 reference source — Tribes 2 addition.
136
154
  if (bs.readFlag()) {
137
- result.shieldEffectScale = {
138
- x: bs.readF32(),
139
- y: bs.readF32(),
140
- z: bs.readF32(),
141
- };
155
+ result.shieldEffectScale = bs.readPoint3F();
142
156
  }
143
157
  // HUD images loop (8 iterations, NumHudRenderImages=8)
144
158
  // Binary: do-while uVar4 < 8 at offset this + uVar4*4 + 0x174
@@ -222,11 +236,7 @@ function shapeBaseImageDataUnpack(bs) {
222
236
  };
223
237
  }
224
238
  // shellExitDir — 3×F32 (mathWrite Point3F)
225
- result.shellExitDir = {
226
- x: bs.readF32(),
227
- y: bs.readF32(),
228
- z: bs.readF32(),
229
- };
239
+ result.shellExitDir = bs.readPoint3F();
230
240
  // shellExitVariance — F32
231
241
  result.shellExitVariance = bs.readF32();
232
242
  // shellVelocity — F32
@@ -366,9 +376,7 @@ function playerDataUnpack(bs) {
366
376
  // [0x364 hardcoded to 0, not from stream]
367
377
  // 3. 2 optional DataBlock refs (readClassId pattern)
368
378
  result.jetEmitter = readDataBlockRef(bs); // 0x368 (jetEmitter @0x364)
369
- if (bs.readFlag()) {
370
- result.jetEffect = bs.readInt(11); // 0x370 (jetEffect @0x36c; no else/-1)
371
- }
379
+ result.jetEffect = readDataBlockRef(bs); // 0x370 (jetEffect @0x36c)
372
380
  // 4. 9× F32 (offsets 0x374-0x394)
373
381
  result.runForce = bs.readF32(); // 0x374
374
382
  result.runEnergyDrain = bs.readF32(); // 0x378
@@ -415,20 +423,11 @@ function playerDataUnpack(bs) {
415
423
  // Binary: zeros 0x428+i*4, then flag + readClassId → 0x4a8+i*4
416
424
  const sounds = [];
417
425
  for (let i = 0; i < 32; i++) {
418
- if (bs.readFlag()) {
419
- sounds.push(bs.readInt(11));
420
- }
421
- else {
422
- sounds.push(null);
423
- }
426
+ sounds.push(readDataBlockRef(bs));
424
427
  }
425
428
  result.sounds = sounds;
426
429
  // 12. boxSize 3×F32 (offsets 0x528, 0x52c, 0x530)
427
- result.boxSize = {
428
- x: bs.readF32(),
429
- y: bs.readF32(),
430
- z: bs.readF32(),
431
- };
430
+ result.boxSize = bs.readPoint3F();
432
431
  // 13. footPuffEmitter + 2 F32s (offset 0xcf0, 0xcf4, 0xcf8)
433
432
  result.footPuffEmitter = readDataBlockRef(bs);
434
433
  result.footPuffNumParts = bs.readF32();
@@ -524,16 +523,8 @@ function vehicleDataUnpack(bs) {
524
523
  }
525
524
  result.splashEmitters = splashEmitters;
526
525
  // 8. 2×3 F32 damage emitter offsets (2 sets of x,y,z) → offsets 0x3e8-0x3f4
527
- result.damageEmitterOffset0 = {
528
- x: bs.readF32(),
529
- y: bs.readF32(),
530
- z: bs.readF32(),
531
- };
532
- result.damageEmitterOffset1 = {
533
- x: bs.readF32(),
534
- y: bs.readF32(),
535
- z: bs.readF32(),
536
- };
526
+ result.damageEmitterOffset0 = bs.readPoint3F();
527
+ result.damageEmitterOffset1 = bs.readPoint3F();
537
528
  // 9. 2 F32 damage level tolerance → offsets 0x400, 0x404
538
529
  result.damageLevelTolerance0 = bs.readF32();
539
530
  result.damageLevelTolerance1 = bs.readF32();
@@ -602,11 +593,7 @@ function hoverVehicleDataUnpack(bs) {
602
593
  result.floatingThrustFactor = bs.readF32();
603
594
  result.brakingForce = bs.readF32();
604
595
  // 3×F32 (dustTrailOffset)
605
- result.dustTrailOffset = {
606
- x: bs.readF32(),
607
- y: bs.readF32(),
608
- z: bs.readF32(),
609
- };
596
+ result.dustTrailOffset = bs.readPoint3F();
610
597
  // 2 F32s
611
598
  result.dustTrailFreqMod = bs.readF32();
612
599
  result.triggerTrailHeight = bs.readF32();
@@ -748,11 +735,7 @@ function projectileDataUnpack(bs) {
748
735
  result.faceViewer = bs.readFlag();
749
736
  // flag(nonDefaultScale); if true: 3×F32
750
737
  if (bs.readFlag()) {
751
- result.scale = {
752
- x: bs.readF32(),
753
- y: bs.readF32(),
754
- z: bs.readF32(),
755
- };
738
+ result.scale = bs.readPoint3F();
756
739
  }
757
740
  // 9 readDataBlockRef fields in pack order (verified against decompiled
758
741
  // Tribes2.exe FUN_00631010 / FUN_006303f0 struct offsets).
@@ -1087,19 +1070,19 @@ function energyProjectileDataUnpack(bs) {
1087
1070
  // LinearFlareProjectileData (extends LinearProjectileData)
1088
1071
  // ============================================================
1089
1072
  function linearFlareProjectileDataUnpack(bs) {
1090
- // Verified against decompiled binary FUN_0063dc80
1073
+ // Bit layout from unpackData FUN_0063dc80; names from initPersistFields
1074
+ // FUN_0063d870 (numFlares 0x168 S32, size[3] 0x16c F32, flareColor
1075
+ // 0x178, flareModTexture 0x188, flareBaseTexture 0x18c).
1091
1076
  const result = linearProjectileDataUnpack(bs);
1092
- // F32 (offset 0x168)
1093
- result.numFlares = bs.readF32();
1077
+ // S32 (offset 0x168): raw 4-byte read of an integer field.
1078
+ result.numFlares = bs.readS32();
1094
1079
  // ColorF via FUN_0043f040 (packed 4×U8 at offset 0x178)
1095
1080
  result.flareColor = readColorF(bs);
1096
1081
  // 2 readString (offsets 0x188, 0x18c)
1097
- result.flareTexture = bs.readString();
1098
- result.smokeTexture = bs.readString();
1099
- // 3×F32 loop (offset 0x16c, count=3)
1100
- result.size = bs.readF32();
1101
- result.flareModTexture = bs.readF32();
1102
- result.smokeSize = bs.readF32();
1082
+ result.flareModTexture = bs.readString();
1083
+ result.flareBaseTexture = bs.readString();
1084
+ // 3×F32 loop (offset 0x16c, count=3): size[0..2].
1085
+ result.sizes = [bs.readF32(), bs.readF32(), bs.readF32()];
1103
1086
  return result;
1104
1087
  }
1105
1088
  // ============================================================
@@ -1175,16 +1158,8 @@ function explosionDataUnpack(bs) {
1175
1158
  result.shakeCamera = bs.readFlag();
1176
1159
  result.hasLight = bs.readFlag();
1177
1160
  // 9×F32
1178
- result.camShakeFreq = {
1179
- x: bs.readF32(),
1180
- y: bs.readF32(),
1181
- z: bs.readF32(),
1182
- };
1183
- result.camShakeAmp = {
1184
- x: bs.readF32(),
1185
- y: bs.readF32(),
1186
- z: bs.readF32(),
1187
- };
1161
+ result.camShakeFreq = bs.readPoint3F();
1162
+ result.camShakeAmp = bs.readPoint3F();
1188
1163
  result.camShakeDuration = bs.readF32();
1189
1164
  result.camShakeRadius = bs.readF32();
1190
1165
  result.camShakeFalloff = bs.readF32();
@@ -1238,9 +1213,11 @@ function debrisDataUnpack(bs) {
1238
1213
  result.snapOnMaxBounce = readBool(bs);
1239
1214
  result.lifetime = bs.readF32(); // 0x54
1240
1215
  result.lifetimeVariance = bs.readF32(); // 0x58
1241
- // "Written twice" bug: same offsets 0x64/0x68 written again
1242
- result.minSpinSpeed_dup = bs.readF32();
1243
- result.maxSpinSpeed_dup = bs.readF32();
1216
+ // The binary writes minSpinSpeed/maxSpinSpeed (0x64/0x68) a second
1217
+ // time here; the client's second read overwrites the first, so do the
1218
+ // same.
1219
+ result.minSpinSpeed = bs.readF32();
1220
+ result.maxSpinSpeed = bs.readF32();
1244
1221
  result.velocity = bs.readF32(); // 0x44
1245
1222
  result.velocityVariance = bs.readF32(); // 0x48
1246
1223
  // 2 bools via stream->write(sizeof(bool)) = 8 bits each (0x70, 0x71)
@@ -1266,11 +1243,7 @@ function debrisDataUnpack(bs) {
1266
1243
  function splashDataUnpack(bs) {
1267
1244
  const result = {};
1268
1245
  // 3×F32 (scale)
1269
- result.scale = {
1270
- x: bs.readF32(),
1271
- y: bs.readF32(),
1272
- z: bs.readF32(),
1273
- };
1246
+ result.scale = bs.readPoint3F();
1274
1247
  // ~15 F32/S32 values
1275
1248
  result.delayMS = bs.readS32();
1276
1249
  result.delayVariance = bs.readS32();
@@ -1317,11 +1290,7 @@ function splashDataUnpack(bs) {
1317
1290
  function shockwaveDataUnpack(bs) {
1318
1291
  const result = {};
1319
1292
  // 3×F32 (scale)
1320
- result.scale = {
1321
- x: bs.readF32(),
1322
- y: bs.readF32(),
1323
- z: bs.readF32(),
1324
- };
1293
+ result.scale = bs.readPoint3F();
1325
1294
  // ~17 F32/S32/bool values
1326
1295
  result.delayMS = bs.readS32();
1327
1296
  result.delayVariance = bs.readS32();
@@ -1401,9 +1370,11 @@ function particleEmitterDataUnpack(bs) {
1401
1370
  // Verified against decompiled binary FUN_006222a0:
1402
1371
  // read(4, &count) → U32 count
1403
1372
  // for each: readFlag → if true: FUN_00436d10 (readClassId, 11 bits); else: 0xFFFFFFFF
1404
- const particleCount = bs.readU32();
1373
+ // particleEngine.cc sizes the vector straight from the count (no cap);
1374
+ // each entry is at least the 1-bit presence flag.
1375
+ const particleCount = readCheckedCount(bs, 1, "ParticleEmitterData particle count");
1405
1376
  const particles = [];
1406
- for (let i = 0; i < particleCount && i < 16; i++) {
1377
+ for (let i = 0; i < particleCount; i++) {
1407
1378
  particles.push(readDataBlockRef(bs));
1408
1379
  }
1409
1380
  result.particles = particles;
@@ -1454,10 +1425,14 @@ function particleDataUnpack(bs) {
1454
1425
  });
1455
1426
  }
1456
1427
  result.keys = keys;
1457
- // readInt(6) texCount; per tex: readString
1428
+ // readInt(6) texCount; per tex: readString. The writer only emits up
1429
+ // to PDC_MAX_TEX names, so a larger count means the stream is corrupt.
1458
1430
  const texCount = bs.readInt(6);
1431
+ if (texCount > ParticleDataMaxTextures) {
1432
+ throw new Error(`Invalid ParticleData texture count: ${texCount}`);
1433
+ }
1459
1434
  const textures = [];
1460
- for (let i = 0; i < texCount && i < 50; i++) {
1435
+ for (let i = 0; i < texCount; i++) {
1461
1436
  textures.push(bs.readString());
1462
1437
  }
1463
1438
  result.textures = textures;
@@ -1805,7 +1780,7 @@ function tsShapeConstructorUnpack(bs) {
1805
1780
  result.shape = bs.readString();
1806
1781
  const count = bs.readInt(7);
1807
1782
  const sequences = [];
1808
- for (let i = 0; i < count && i < 128; i++) {
1783
+ for (let i = 0; i < count; i++) {
1809
1784
  sequences.push(bs.readString());
1810
1785
  }
1811
1786
  result.sequences = sequences;