@kar-mi/spirit-vale-tools-capture 2.6.0 → 3.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.
Files changed (143) hide show
  1. package/dist/capture/drop-diagnostics.d.ts +42 -0
  2. package/dist/capture/drop-diagnostics.d.ts.map +1 -0
  3. package/dist/capture/duplicate-filter.d.ts +39 -0
  4. package/dist/capture/duplicate-filter.d.ts.map +1 -0
  5. package/dist/capture/packet-capture.d.ts +11 -0
  6. package/dist/capture/packet-capture.d.ts.map +1 -1
  7. package/dist/capture.js +351 -130
  8. package/dist/fishnet/{decoder.d.ts → decoding/decoder.d.ts} +2 -2
  9. package/dist/fishnet/decoding/decoder.d.ts.map +1 -0
  10. package/dist/fishnet/{field-decoder.d.ts → decoding/fields.d.ts} +2 -2
  11. package/dist/fishnet/decoding/fields.d.ts.map +1 -0
  12. package/dist/fishnet/{message-parser.d.ts → decoding/messages.d.ts} +2 -2
  13. package/dist/fishnet/{message-parser.d.ts.map → decoding/messages.d.ts.map} +1 -1
  14. package/dist/fishnet/{network-transform-decoder.d.ts → decoding/network-transform.d.ts} +2 -2
  15. package/dist/fishnet/decoding/network-transform.d.ts.map +1 -0
  16. package/dist/fishnet/{protocol.d.ts → decoding/protocol.d.ts} +1 -1
  17. package/dist/fishnet/decoding/protocol.d.ts.map +1 -0
  18. package/dist/fishnet/{quaternion-compression.d.ts → decoding/quaternion.d.ts} +1 -1
  19. package/dist/fishnet/decoding/quaternion.d.ts.map +1 -0
  20. package/dist/fishnet/{session-decoder.d.ts → decoding/session.d.ts} +2 -2
  21. package/dist/fishnet/decoding/session.d.ts.map +1 -0
  22. package/dist/fishnet/{spawn-parser.d.ts → decoding/spawn.d.ts} +2 -2
  23. package/dist/fishnet/decoding/spawn.d.ts.map +1 -0
  24. package/dist/fishnet/decoding/wire-reader.d.ts.map +1 -0
  25. package/dist/fishnet/{wire-reader.js → decoding/wire-reader.js} +2 -2
  26. package/dist/fishnet/generated/map-names.current.d.ts +3 -0
  27. package/dist/fishnet/generated/map-names.current.d.ts.map +1 -0
  28. package/dist/fishnet/generated/rpc-map/base-unit-controller.d.ts.map +1 -0
  29. package/dist/fishnet/generated/rpc-map/boss-grave-stone.d.ts.map +1 -0
  30. package/dist/fishnet/generated/rpc-map/broadcasts.d.ts.map +1 -0
  31. package/dist/fishnet/generated/rpc-map/combat-component.d.ts.map +1 -0
  32. package/dist/fishnet/{rpc-definitions/generated/index.d.ts → generated/rpc-map/definitions.d.ts} +53 -1
  33. package/dist/fishnet/{rpc-definitions/generated/index.d.ts.map → generated/rpc-map/definitions.d.ts.map} +1 -1
  34. package/dist/fishnet/generated/rpc-map/engine/network-animator.d.ts.map +1 -0
  35. package/dist/fishnet/generated/rpc-map/engine/network-transform.d.ts.map +1 -0
  36. package/dist/fishnet/generated/rpc-map/engine/predicted-owner.d.ts.map +1 -0
  37. package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/health-component.d.ts +17 -0
  38. package/dist/fishnet/generated/rpc-map/health-component.d.ts.map +1 -0
  39. package/dist/fishnet/{rpc-definitions → generated/rpc-map}/index.d.ts +52 -0
  40. package/dist/fishnet/{rpc-definitions → generated/rpc-map}/index.d.ts.map +1 -1
  41. package/dist/fishnet/generated/rpc-map/loot-drop.d.ts.map +1 -0
  42. package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/manifest.d.ts +1 -1
  43. package/dist/fishnet/generated/rpc-map/manifest.d.ts.map +1 -0
  44. package/dist/fishnet/generated/rpc-map/monster-controller.d.ts.map +1 -0
  45. package/dist/fishnet/generated/rpc-map/move-component.d.ts.map +1 -0
  46. package/dist/fishnet/generated/rpc-map/player-controller.d.ts.map +1 -0
  47. package/dist/fishnet/generated/rpc-map/player-save.d.ts.map +1 -0
  48. package/dist/fishnet/generated/rpc-map/prefabs.d.ts.map +1 -0
  49. package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/skills-component.d.ts +35 -0
  50. package/dist/fishnet/generated/rpc-map/skills-component.d.ts.map +1 -0
  51. package/dist/fishnet/generated/rpc-map/status-component.d.ts.map +1 -0
  52. package/dist/fishnet/generated/rpc-map/summoning-component.d.ts.map +1 -0
  53. package/dist/fishnet/inference/rpc-inference.d.ts +9 -0
  54. package/dist/fishnet/inference/rpc-inference.d.ts.map +1 -0
  55. package/dist/fishnet/{builtin-maps.d.ts → mapping/bundled-rpc-map.d.ts} +54 -2
  56. package/dist/fishnet/{builtin-maps.d.ts.map → mapping/bundled-rpc-map.d.ts.map} +1 -1
  57. package/dist/fishnet/{map-definitions/index.d.ts → mapping/map-names.d.ts} +1 -1
  58. package/dist/fishnet/mapping/map-names.d.ts.map +1 -0
  59. package/dist/fishnet/mapping/rpc-map.d.ts +15 -0
  60. package/dist/fishnet/mapping/rpc-map.d.ts.map +1 -0
  61. package/dist/fishnet/schema/character-data.d.ts +3 -0
  62. package/dist/fishnet/schema/character-data.d.ts.map +1 -0
  63. package/dist/fishnet/schema/codecs.d.ts.map +1 -0
  64. package/dist/fishnet/{definitions/protocol.d.ts → schema/packets.d.ts} +2 -2
  65. package/dist/fishnet/schema/packets.d.ts.map +1 -0
  66. package/dist/fishnet/{definitions → schema}/rpc-map.d.ts +1 -1
  67. package/dist/fishnet/schema/rpc-map.d.ts.map +1 -0
  68. package/dist/fishnet/{boss-gravestone.d.ts → tracking/boss-gravestone.d.ts} +1 -1
  69. package/dist/fishnet/tracking/boss-gravestone.d.ts.map +1 -0
  70. package/dist/fishnet/{eternal-tower-tracker.d.ts → tracking/eternal-tower.d.ts} +2 -2
  71. package/dist/fishnet/tracking/eternal-tower.d.ts.map +1 -0
  72. package/dist/fishnet/{monster-directory.d.ts → tracking/monsters.d.ts} +2 -2
  73. package/dist/fishnet/tracking/monsters.d.ts.map +1 -0
  74. package/dist/fishnet/types.d.ts +4 -4
  75. package/dist/fishnet/types.d.ts.map +1 -1
  76. package/dist/index.d.ts +16 -18
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +197 -179
  79. package/dist/types.d.ts +7 -0
  80. package/dist/types.d.ts.map +1 -1
  81. package/package.json +4 -4
  82. package/dist/fishnet/boss-gravestone.d.ts.map +0 -1
  83. package/dist/fishnet/character-data-schema.d.ts +0 -3
  84. package/dist/fishnet/character-data-schema.d.ts.map +0 -1
  85. package/dist/fishnet/decoder.d.ts.map +0 -1
  86. package/dist/fishnet/definitions/codecs.d.ts.map +0 -1
  87. package/dist/fishnet/definitions/protocol.d.ts.map +0 -1
  88. package/dist/fishnet/definitions/rpc-map.d.ts.map +0 -1
  89. package/dist/fishnet/eternal-tower-tracker.d.ts.map +0 -1
  90. package/dist/fishnet/field-decoder.d.ts.map +0 -1
  91. package/dist/fishnet/map-definitions/current-build.d.ts +0 -3
  92. package/dist/fishnet/map-definitions/current-build.d.ts.map +0 -1
  93. package/dist/fishnet/map-definitions/index.d.ts.map +0 -1
  94. package/dist/fishnet/monster-directory.d.ts.map +0 -1
  95. package/dist/fishnet/network-transform-decoder.d.ts.map +0 -1
  96. package/dist/fishnet/protocol.d.ts.map +0 -1
  97. package/dist/fishnet/quaternion-compression.d.ts.map +0 -1
  98. package/dist/fishnet/recovery-style.d.ts +0 -5
  99. package/dist/fishnet/recovery-style.d.ts.map +0 -1
  100. package/dist/fishnet/rpc-definitions/generated/base-unit-controller.d.ts.map +0 -1
  101. package/dist/fishnet/rpc-definitions/generated/boss-grave-stone.d.ts.map +0 -1
  102. package/dist/fishnet/rpc-definitions/generated/broadcasts.d.ts.map +0 -1
  103. package/dist/fishnet/rpc-definitions/generated/combat-component.d.ts.map +0 -1
  104. package/dist/fishnet/rpc-definitions/generated/engine/network-animator.d.ts.map +0 -1
  105. package/dist/fishnet/rpc-definitions/generated/engine/network-transform.d.ts.map +0 -1
  106. package/dist/fishnet/rpc-definitions/generated/engine/predicted-owner.d.ts.map +0 -1
  107. package/dist/fishnet/rpc-definitions/generated/health-component.d.ts.map +0 -1
  108. package/dist/fishnet/rpc-definitions/generated/loot-drop.d.ts.map +0 -1
  109. package/dist/fishnet/rpc-definitions/generated/manifest.d.ts.map +0 -1
  110. package/dist/fishnet/rpc-definitions/generated/monster-controller.d.ts.map +0 -1
  111. package/dist/fishnet/rpc-definitions/generated/move-component.d.ts.map +0 -1
  112. package/dist/fishnet/rpc-definitions/generated/player-controller.d.ts.map +0 -1
  113. package/dist/fishnet/rpc-definitions/generated/player-save.d.ts.map +0 -1
  114. package/dist/fishnet/rpc-definitions/generated/prefabs.d.ts.map +0 -1
  115. package/dist/fishnet/rpc-definitions/generated/skills-component.d.ts.map +0 -1
  116. package/dist/fishnet/rpc-definitions/generated/status-component.d.ts.map +0 -1
  117. package/dist/fishnet/rpc-definitions/generated/summoning-component.d.ts.map +0 -1
  118. package/dist/fishnet/rpc-resolution.d.ts +0 -65
  119. package/dist/fishnet/rpc-resolution.d.ts.map +0 -1
  120. package/dist/fishnet/semantic-definitions.d.ts +0 -40
  121. package/dist/fishnet/semantic-definitions.d.ts.map +0 -1
  122. package/dist/fishnet/semantic-map.d.ts +0 -23
  123. package/dist/fishnet/semantic-map.d.ts.map +0 -1
  124. package/dist/fishnet/session-decoder.d.ts.map +0 -1
  125. package/dist/fishnet/spawn-parser.d.ts.map +0 -1
  126. package/dist/fishnet/wire-reader.d.ts.map +0 -1
  127. /package/dist/fishnet/{wire-reader.d.ts → decoding/wire-reader.d.ts} +0 -0
  128. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/base-unit-controller.d.ts +0 -0
  129. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/boss-grave-stone.d.ts +0 -0
  130. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/broadcasts.d.ts +0 -0
  131. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/combat-component.d.ts +0 -0
  132. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/engine/network-animator.d.ts +0 -0
  133. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/engine/network-transform.d.ts +0 -0
  134. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/engine/predicted-owner.d.ts +0 -0
  135. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/loot-drop.d.ts +0 -0
  136. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/monster-controller.d.ts +0 -0
  137. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/move-component.d.ts +0 -0
  138. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/player-controller.d.ts +0 -0
  139. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/player-save.d.ts +0 -0
  140. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/prefabs.d.ts +0 -0
  141. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/status-component.d.ts +0 -0
  142. /package/dist/fishnet/{rpc-definitions/generated → generated/rpc-map}/summoning-component.d.ts +0 -0
  143. /package/dist/fishnet/{definitions → schema}/codecs.d.ts +0 -0
package/dist/capture.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- // src/fishnet/protocol.ts
2
+ // src/fishnet/decoding/protocol.ts
3
3
  var PACKET_NAMES = new Map([
4
4
  [0, "unset"],
5
5
  [1, "authenticated"],
@@ -70,7 +70,7 @@ function unresolvedLinkPacket(buffer, start, tick, bundleIndex, packetId) {
70
70
  return packet;
71
71
  }
72
72
 
73
- // src/fishnet/wire-reader.ts
73
+ // src/fishnet/decoding/wire-reader.ts
74
74
  function requireBytes(buffer, offset, count, description) {
75
75
  if (buffer.length - offset < count) {
76
76
  throw new FishNetProtocolError(`${description} needs ${count} bytes at byte ${offset}; ${buffer.length - offset} remain`);
@@ -277,7 +277,7 @@ function requireLength(bytes, offset, length, absoluteOffset, field) {
277
277
  }
278
278
  }
279
279
 
280
- // src/fishnet/rpc-definitions/generated/base-unit-controller.ts
280
+ // src/fishnet/generated/rpc-map/base-unit-controller.ts
281
281
  var baseUnitControllerRpcs = [
282
282
  {
283
283
  wireHash: 0,
@@ -379,7 +379,7 @@ var baseUnitControllerRpcs = [
379
379
  }
380
380
  ];
381
381
 
382
- // src/fishnet/rpc-definitions/generated/boss-grave-stone.ts
382
+ // src/fishnet/generated/rpc-map/boss-grave-stone.ts
383
383
  var bossGraveStoneRpcs = [];
384
384
  var bossGraveStoneSyncTypes = [
385
385
  {
@@ -411,7 +411,7 @@ var bossGraveStoneSyncTypes = [
411
411
  }
412
412
  ];
413
413
 
414
- // src/fishnet/rpc-definitions/generated/combat-component.ts
414
+ // src/fishnet/generated/rpc-map/combat-component.ts
415
415
  var combatComponentRpcs = [
416
416
  {
417
417
  wireHash: 0,
@@ -516,7 +516,7 @@ var combatComponentSyncTypes = [
516
516
  }
517
517
  ];
518
518
 
519
- // src/fishnet/rpc-definitions/generated/engine/network-animator.ts
519
+ // src/fishnet/generated/rpc-map/engine/network-animator.ts
520
520
  var networkAnimatorRpcs = [
521
521
  {
522
522
  wireHash: 0,
@@ -542,7 +542,7 @@ var networkAnimatorRpcs = [
542
542
  }
543
543
  ];
544
544
 
545
- // src/fishnet/rpc-definitions/generated/engine/predicted-owner.ts
545
+ // src/fishnet/generated/rpc-map/engine/predicted-owner.ts
546
546
  var predictedOwnerRpcs = [
547
547
  {
548
548
  wireHash: 0,
@@ -558,7 +558,7 @@ var predictedOwnerRpcs = [
558
558
  }
559
559
  ];
560
560
 
561
- // src/fishnet/rpc-definitions/generated/engine/network-transform.ts
561
+ // src/fishnet/generated/rpc-map/engine/network-transform.ts
562
562
  var networkTransformRpcs = [
563
563
  {
564
564
  wireHash: 0,
@@ -651,7 +651,7 @@ var networkTransformRpcs = [
651
651
  }
652
652
  ];
653
653
 
654
- // src/fishnet/rpc-definitions/generated/health-component.ts
654
+ // src/fishnet/generated/rpc-map/health-component.ts
655
655
  var healthComponentRpcs = [
656
656
  {
657
657
  wireHash: 0,
@@ -748,7 +748,29 @@ var healthComponentRpcs = [
748
748
  },
749
749
  {
750
750
  name: "settings",
751
- typeName: "FloaterSettings"
751
+ typeName: "FloaterSettings",
752
+ fields: [
753
+ {
754
+ name: "DisableFloater",
755
+ typeName: "System.Boolean",
756
+ codec: "boolean"
757
+ },
758
+ {
759
+ name: "DisableSfx",
760
+ typeName: "System.Boolean",
761
+ codec: "boolean"
762
+ },
763
+ {
764
+ name: "Offset",
765
+ typeName: "System.Int32",
766
+ codec: "packedInt32"
767
+ },
768
+ {
769
+ name: "Scale",
770
+ typeName: "System.Single",
771
+ codec: "float32"
772
+ }
773
+ ]
752
774
  }
753
775
  ]
754
776
  },
@@ -853,7 +875,7 @@ var healthComponentSyncTypes = [
853
875
  }
854
876
  ];
855
877
 
856
- // src/fishnet/rpc-definitions/generated/loot-drop.ts
878
+ // src/fishnet/generated/rpc-map/loot-drop.ts
857
879
  var lootDropRpcs = [];
858
880
  var lootDropSyncTypes = [
859
881
  {
@@ -917,7 +939,7 @@ var lootDropSyncTypes = [
917
939
  }
918
940
  ];
919
941
 
920
- // src/fishnet/rpc-definitions/generated/monster-controller.ts
942
+ // src/fishnet/generated/rpc-map/monster-controller.ts
921
943
  var monsterControllerRpcs = [
922
944
  {
923
945
  wireHash: 7,
@@ -1065,7 +1087,7 @@ var monsterControllerSyncTypes = [
1065
1087
  }
1066
1088
  ];
1067
1089
 
1068
- // src/fishnet/rpc-definitions/generated/move-component.ts
1090
+ // src/fishnet/generated/rpc-map/move-component.ts
1069
1091
  var moveComponentRpcs = [
1070
1092
  {
1071
1093
  wireHash: 0,
@@ -1129,7 +1151,7 @@ var moveComponentSyncTypes = [
1129
1151
  }
1130
1152
  ];
1131
1153
 
1132
- // src/fishnet/rpc-definitions/generated/player-controller.ts
1154
+ // src/fishnet/generated/rpc-map/player-controller.ts
1133
1155
  var playerControllerRpcs = [
1134
1156
  {
1135
1157
  wireHash: 7,
@@ -5354,7 +5376,7 @@ var playerControllerSyncTypes = [
5354
5376
  }
5355
5377
  ];
5356
5378
 
5357
- // src/fishnet/rpc-definitions/generated/player-save.ts
5379
+ // src/fishnet/generated/rpc-map/player-save.ts
5358
5380
  var playerSaveRpcs = [
5359
5381
  {
5360
5382
  wireHash: 0,
@@ -13024,7 +13046,7 @@ var playerSaveSyncTypes = [
13024
13046
  }
13025
13047
  ];
13026
13048
 
13027
- // src/fishnet/rpc-definitions/generated/skills-component.ts
13049
+ // src/fishnet/generated/rpc-map/skills-component.ts
13028
13050
  var skillsComponentRpcs = [
13029
13051
  {
13030
13052
  wireHash: 0,
@@ -13050,7 +13072,29 @@ var skillsComponentRpcs = [
13050
13072
  },
13051
13073
  {
13052
13074
  name: "settings",
13053
- typeName: "FloaterSettings"
13075
+ typeName: "FloaterSettings",
13076
+ fields: [
13077
+ {
13078
+ name: "DisableFloater",
13079
+ typeName: "System.Boolean",
13080
+ codec: "boolean"
13081
+ },
13082
+ {
13083
+ name: "DisableSfx",
13084
+ typeName: "System.Boolean",
13085
+ codec: "boolean"
13086
+ },
13087
+ {
13088
+ name: "Offset",
13089
+ typeName: "System.Int32",
13090
+ codec: "packedInt32"
13091
+ },
13092
+ {
13093
+ name: "Scale",
13094
+ typeName: "System.Single",
13095
+ codec: "float32"
13096
+ }
13097
+ ]
13054
13098
  }
13055
13099
  ]
13056
13100
  },
@@ -13318,11 +13362,35 @@ var skillsComponentSyncTypes = [
13318
13362
  {
13319
13363
  index: 2,
13320
13364
  name: "BondSync",
13321
- typeName: "BondDto"
13365
+ typeName: "BondDto",
13366
+ fields: [
13367
+ {
13368
+ name: "Entries",
13369
+ typeName: "System.Collections.Generic.List`1[[SkillsComponent+BondEntry, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]",
13370
+ repeated: true,
13371
+ fields: [
13372
+ {
13373
+ name: "Other",
13374
+ typeName: "FishNet.Object.NetworkObject",
13375
+ codec: "networkObject"
13376
+ },
13377
+ {
13378
+ name: "SkillId",
13379
+ typeName: "System.String",
13380
+ codec: "stringUtf8Packed"
13381
+ },
13382
+ {
13383
+ name: "Caster",
13384
+ typeName: "System.Boolean",
13385
+ codec: "boolean"
13386
+ }
13387
+ ]
13388
+ }
13389
+ ]
13322
13390
  }
13323
13391
  ];
13324
13392
 
13325
- // src/fishnet/rpc-definitions/generated/status-component.ts
13393
+ // src/fishnet/generated/rpc-map/status-component.ts
13326
13394
  var statusComponentRpcs = [
13327
13395
  {
13328
13396
  wireHash: 0,
@@ -13469,7 +13537,7 @@ var statusComponentSyncTypes = [
13469
13537
  }
13470
13538
  ];
13471
13539
 
13472
- // src/fishnet/rpc-definitions/generated/summoning-component.ts
13540
+ // src/fishnet/generated/rpc-map/summoning-component.ts
13473
13541
  var summoningComponentRpcs = [
13474
13542
  {
13475
13543
  wireHash: 0,
@@ -13584,7 +13652,7 @@ var summoningComponentSyncTypes = [
13584
13652
  }
13585
13653
  ];
13586
13654
 
13587
- // src/fishnet/rpc-definitions/generated/broadcasts.ts
13655
+ // src/fishnet/generated/rpc-map/broadcasts.ts
13588
13656
  var GENERATED_BROADCASTS = [
13589
13657
  {
13590
13658
  wireHash: 40743,
@@ -13611,7 +13679,7 @@ var GENERATED_BROADCASTS = [
13611
13679
  typeName: "FishNet.Managing.Server.ConnectedClientsBroadcast"
13612
13680
  }
13613
13681
  ];
13614
- // src/fishnet/rpc-definitions/generated/prefabs.ts
13682
+ // src/fishnet/generated/rpc-map/prefabs.ts
13615
13683
  var GENERATED_PREFAB_DEFINITIONS = [
13616
13684
  {
13617
13685
  collectionId: 0,
@@ -13772,11 +13840,11 @@ var GENERATED_PREFAB_DEFINITIONS = [
13772
13840
  ]
13773
13841
  }
13774
13842
  ];
13775
- // src/fishnet/rpc-definitions/generated/manifest.ts
13843
+ // src/fishnet/generated/rpc-map/manifest.ts
13776
13844
  var GENERATED_BUILD_FINGERPRINT = "53305f369c4176d4b7644d06ad7f0cdc5c9bda6563144d0ce8cff3d12c520158";
13777
13845
  var GENERATED_METADATA_VERSION = 31;
13778
13846
 
13779
- // src/fishnet/rpc-definitions/generated/index.ts
13847
+ // src/fishnet/generated/rpc-map/definitions.ts
13780
13848
  var GENERATED_BEHAVIOURS = [
13781
13849
  { typeName: "BaseUnitController", rpcs: baseUnitControllerRpcs },
13782
13850
  { typeName: "BossGraveStone", rpcs: bossGraveStoneRpcs, syncTypes: bossGraveStoneSyncTypes },
@@ -13795,7 +13863,7 @@ var GENERATED_BEHAVIOURS = [
13795
13863
  { typeName: "SummoningComponent", rpcs: summoningComponentRpcs, syncTypes: summoningComponentSyncTypes }
13796
13864
  ];
13797
13865
 
13798
- // src/fishnet/rpc-definitions/index.ts
13866
+ // src/fishnet/generated/rpc-map/index.ts
13799
13867
  var FISHNET_RPC_MAP = {
13800
13868
  buildFingerprint: GENERATED_BUILD_FINGERPRINT,
13801
13869
  metadataVersion: GENERATED_METADATA_VERSION,
@@ -13810,7 +13878,7 @@ var BUNDLED_GAME_BUILD_FINGERPRINTS = [
13810
13878
  CURRENT_GAME_BUILD_FINGERPRINT
13811
13879
  ];
13812
13880
 
13813
- // src/fishnet/builtin-maps.ts
13881
+ // src/fishnet/mapping/bundled-rpc-map.ts
13814
13882
  var MAPS = {
13815
13883
  [CURRENT_GAME_BUILD_FINGERPRINT]: FISHNET_RPC_MAP
13816
13884
  };
@@ -13824,7 +13892,7 @@ function isBundledFingerprint(value) {
13824
13892
  return Object.hasOwn(MAPS, value);
13825
13893
  }
13826
13894
 
13827
- // src/fishnet/map-definitions/current-build.ts
13895
+ // src/fishnet/generated/map-names.current.ts
13828
13896
  var CURRENT_BUILD_MAP_NAMES = {
13829
13897
  1: "Nevaris",
13830
13898
  2: "Sunny Meadows 1",
@@ -13884,7 +13952,7 @@ var CURRENT_BUILD_MAP_NAMES = {
13884
13952
  56: "Jail"
13885
13953
  };
13886
13954
 
13887
- // src/fishnet/map-definitions/index.ts
13955
+ // src/fishnet/mapping/map-names.ts
13888
13956
  var MAPS2 = {
13889
13957
  [CURRENT_GAME_BUILD_FINGERPRINT]: CURRENT_BUILD_MAP_NAMES
13890
13958
  };
@@ -13892,7 +13960,7 @@ function resolveBundledMapName(mapId, buildFingerprint = CURRENT_GAME_BUILD_FING
13892
13960
  return MAPS2[buildFingerprint]?.[mapId];
13893
13961
  }
13894
13962
 
13895
- // src/fishnet/field-decoder.ts
13963
+ // src/fishnet/decoding/fields.ts
13896
13964
  function applyDecodedFields(packet, parameters, startOffset = 0) {
13897
13965
  if (!parameters || parameters.length === 0)
13898
13966
  return;
@@ -14128,7 +14196,7 @@ function decodeField(buffer, offset, codec) {
14128
14196
  }
14129
14197
  }
14130
14198
 
14131
- // src/fishnet/quaternion-compression.ts
14199
+ // src/fishnet/decoding/quaternion.ts
14132
14200
  var MAXIMUM = 1 / 1.414214;
14133
14201
  var SCALE_10_BIT = (1 << 9) - 1;
14134
14202
  var SCALE_21_BIT = (1 << 20) - 1;
@@ -14182,7 +14250,7 @@ function quaternionYaw(quaternion) {
14182
14250
  return Math.atan2(2 * (w * y + x * z), 1 - 2 * (y * y + x * x));
14183
14251
  }
14184
14252
 
14185
- // src/fishnet/network-transform-decoder.ts
14253
+ // src/fishnet/decoding/network-transform.ts
14186
14254
  var COMPRESSED_SCALE = 100;
14187
14255
  var ROTATION_WIDTHS = [16, 8, 4];
14188
14256
  var AXIS_FLAGS = [
@@ -14303,7 +14371,94 @@ var NETWORK_TRANSFORM_RPC_NAMES = new Set([
14303
14371
  "ServerUpdateTransform"
14304
14372
  ]);
14305
14373
 
14306
- // src/fishnet/rpc-resolution.ts
14374
+ // src/fishnet/mapping/rpc-map.ts
14375
+ function lookupRpc(map, networkBehaviourType, packetName, hash8, hash16) {
14376
+ if (!map)
14377
+ return { resolution: "unresolved" };
14378
+ const hashes = new Set([hash8, ...hash16 === undefined ? [] : [hash16]]);
14379
+ const behaviours = networkBehaviourType ? map.behaviours.filter(({ typeName }) => typeName === networkBehaviourType) : map.behaviours;
14380
+ const matches = behaviours.flatMap(({ typeName, rpcs }) => rpcs.filter((rpc) => rpc.packetKind === packetName && hashes.has(rpc.wireHash)).map((rpc) => ({ ...rpc, typeName })));
14381
+ return definitionLookup(matches);
14382
+ }
14383
+ function signatureAdmitsPayload(lookup, payload) {
14384
+ const fit = tryDecodeFields(payload, lookup.parameters);
14385
+ return fit.undecodable || fit.complete && (fit.consumed === payload.length || admitsVerifiedPrefix(lookup.parameters));
14386
+ }
14387
+ function admitsVerifiedPrefix(parameters) {
14388
+ return parameters?.at(-1)?.prefix === true;
14389
+ }
14390
+ function rejectedByPayload(packet, lookup) {
14391
+ return lookup.resolution === "verified" && !signatureAdmitsPayload(lookup, packet.payload);
14392
+ }
14393
+ function applyRpcLookup(packet, lookup) {
14394
+ if (rejectedByPayload(packet, lookup)) {
14395
+ packet.rpcResolution = "unresolved";
14396
+ return;
14397
+ }
14398
+ packet.rpcResolution = lookup.resolution;
14399
+ if (lookup.resolution !== "verified")
14400
+ return;
14401
+ packet.rpcName = lookup.methodName;
14402
+ applyDecodedFields(packet, lookup.parameters);
14403
+ applyNetworkTransform(packet);
14404
+ }
14405
+ function applyNetworkTransform(packet) {
14406
+ if (packet.rpcName === undefined || !NETWORK_TRANSFORM_RPC_NAMES.has(packet.rpcName))
14407
+ return;
14408
+ const update = decodeNetworkTransformData(packet.payload);
14409
+ if (!update)
14410
+ return;
14411
+ packet.networkTransform = update;
14412
+ const fields = [];
14413
+ for (const [axis, value] of Object.entries(update.position)) {
14414
+ if (value !== undefined)
14415
+ fields.push({ name: `position.${axis}`, typeName: "System.Single", codec: "float32", value });
14416
+ }
14417
+ for (const [axis, value] of Object.entries(update.scale ?? {})) {
14418
+ if (value !== undefined)
14419
+ fields.push({ name: `scale.${axis}`, typeName: "System.Single", codec: "float32", value });
14420
+ }
14421
+ if (fields.length > 0)
14422
+ packet.decodedFields = [...packet.decodedFields ?? [], ...fields];
14423
+ packet.undecodedPayload = update.consumed < packet.payload.length ? packet.payload.subarray(update.consumed) : undefined;
14424
+ }
14425
+ function findBroadcast(map, wireHash) {
14426
+ if (!map)
14427
+ return;
14428
+ const matches = (map.broadcasts ?? []).filter((broadcast) => broadcast.wireHash === wireHash);
14429
+ return matches.length === 1 ? matches[0] : undefined;
14430
+ }
14431
+ function findSyncType(map, typeName, index) {
14432
+ if (!map || !typeName)
14433
+ return;
14434
+ const matches = map.behaviours.filter((behaviour) => behaviour.typeName === typeName).flatMap((behaviour) => behaviour.syncTypes ?? []).filter((sync) => sync.index === index);
14435
+ return matches.length === 1 ? matches[0] : undefined;
14436
+ }
14437
+ function findPrefab(map, collectionId, prefabId) {
14438
+ if (!map?.prefabs)
14439
+ return;
14440
+ const matches = map.prefabs.filter((layout) => layout.collectionId === collectionId && layout.prefabId === prefabId);
14441
+ return matches.length === 1 ? matches[0] : undefined;
14442
+ }
14443
+ function definitionLookup(matches) {
14444
+ const wireHashes = new Set(matches.map(({ wireHash }) => wireHash));
14445
+ if (matches.length === 1 && matches[0]) {
14446
+ return {
14447
+ resolution: "verified",
14448
+ wireHash: matches[0].wireHash,
14449
+ methodName: matches[0].methodName,
14450
+ parameters: matches[0].parameters
14451
+ };
14452
+ }
14453
+ const ambiguousBehaviourTypes = matches.length > 1 ? [...new Set(matches.map(({ typeName }) => typeName).filter((typeName) => typeName !== undefined))] : undefined;
14454
+ return {
14455
+ resolution: matches.length > 1 ? "ambiguous" : "unresolved",
14456
+ ambiguousBehaviourTypes: ambiguousBehaviourTypes?.length ? ambiguousBehaviourTypes : undefined,
14457
+ wireHash: wireHashes.size === 1 ? wireHashes.values().next().value : undefined
14458
+ };
14459
+ }
14460
+
14461
+ // src/fishnet/inference/rpc-inference.ts
14307
14462
  function bindBehaviourTypes(registrations, map) {
14308
14463
  if (!map)
14309
14464
  return [];
@@ -14365,19 +14520,9 @@ function inferBehaviourType(map, packetName, hash8, hash16) {
14365
14520
  if (!map)
14366
14521
  return;
14367
14522
  const hashes = new Set([hash8, ...hash16 === undefined ? [] : [hash16]]);
14368
- const matches = map.behaviours.filter(({ rpcs }) => {
14369
- return rpcs.some((rpc) => rpc.packetKind === packetName && hashes.has(rpc.wireHash));
14370
- });
14523
+ const matches = map.behaviours.filter(({ rpcs }) => rpcs.some((rpc) => rpc.packetKind === packetName && hashes.has(rpc.wireHash)));
14371
14524
  return matches.length === 1 ? matches[0]?.typeName : undefined;
14372
14525
  }
14373
- function lookupRpc(map, networkBehaviourType, packetName, hash8, hash16) {
14374
- if (!map)
14375
- return { resolution: "unresolved" };
14376
- const hashes = new Set([hash8, ...hash16 === undefined ? [] : [hash16]]);
14377
- const behaviours = networkBehaviourType ? map.behaviours.filter(({ typeName }) => typeName === networkBehaviourType) : map.behaviours;
14378
- const matches = behaviours.flatMap(({ typeName, rpcs }) => rpcs.filter((rpc) => rpc.packetKind === packetName && hashes.has(rpc.wireHash)).map((rpc) => ({ ...rpc, typeName })));
14379
- return definitionLookup(matches);
14380
- }
14381
14526
  function eliminateBoundBehaviourTypes(components, objectId, componentIndex, candidates) {
14382
14527
  const selfKey = componentKey(objectId, componentIndex);
14383
14528
  const prefix = `${objectId}:`;
@@ -14438,94 +14583,17 @@ function eliminateByPayloadShape(map, candidates, packetName, hash8, hash16, pay
14438
14583
  const fit = tryDecodeFields(payload, rpc.parameters);
14439
14584
  if (fit.undecodable)
14440
14585
  return;
14441
- if (fit.complete && (fit.consumed === payload.length || admitsVerifiedPrefix(rpc.parameters)))
14586
+ if (fit.complete && (fit.consumed === payload.length || rpc.parameters?.at(-1)?.prefix === true))
14442
14587
  fitting.add(typeName);
14443
14588
  }
14444
14589
  }
14445
14590
  return fitting.size === 1 ? [...fitting][0] : undefined;
14446
14591
  }
14447
- function signatureAdmitsPayload(lookup, payload) {
14448
- const fit = tryDecodeFields(payload, lookup.parameters);
14449
- return fit.undecodable || fit.complete && (fit.consumed === payload.length || admitsVerifiedPrefix(lookup.parameters));
14450
- }
14451
- function admitsVerifiedPrefix(parameters) {
14452
- return parameters?.at(-1)?.prefix === true;
14453
- }
14454
- function rejectedByPayload(packet, lookup) {
14455
- return lookup.resolution === "verified" && !signatureAdmitsPayload(lookup, packet.payload);
14456
- }
14457
- function applyRpcLookup(packet, lookup) {
14458
- if (rejectedByPayload(packet, lookup)) {
14459
- packet.rpcResolution = "unresolved";
14460
- return;
14461
- }
14462
- packet.rpcResolution = lookup.resolution;
14463
- if (lookup.resolution !== "verified")
14464
- return;
14465
- packet.rpcName = lookup.methodName;
14466
- applyDecodedFields(packet, lookup.parameters);
14467
- applyNetworkTransform(packet);
14468
- }
14469
- function applyNetworkTransform(packet) {
14470
- if (packet.rpcName === undefined || !NETWORK_TRANSFORM_RPC_NAMES.has(packet.rpcName))
14471
- return;
14472
- const update = decodeNetworkTransformData(packet.payload);
14473
- if (!update)
14474
- return;
14475
- packet.networkTransform = update;
14476
- const fields = [];
14477
- for (const [axis, value] of Object.entries(update.position)) {
14478
- if (value !== undefined)
14479
- fields.push({ name: `position.${axis}`, typeName: "System.Single", codec: "float32", value });
14480
- }
14481
- for (const [axis, value] of Object.entries(update.scale ?? {})) {
14482
- if (value !== undefined)
14483
- fields.push({ name: `scale.${axis}`, typeName: "System.Single", codec: "float32", value });
14484
- }
14485
- if (fields.length > 0)
14486
- packet.decodedFields = [...packet.decodedFields ?? [], ...fields];
14487
- packet.undecodedPayload = update.consumed < packet.payload.length ? packet.payload.subarray(update.consumed) : undefined;
14488
- }
14489
- function findBroadcast(map, wireHash) {
14490
- if (!map)
14491
- return;
14492
- const matches = (map.broadcasts ?? []).filter((broadcast) => broadcast.wireHash === wireHash);
14493
- return matches.length === 1 ? matches[0] : undefined;
14494
- }
14495
- function findSyncType(map, typeName, index) {
14496
- if (!map || !typeName)
14497
- return;
14498
- const matches = map.behaviours.filter((behaviour) => behaviour.typeName === typeName).flatMap((behaviour) => behaviour.syncTypes ?? []).filter((sync) => sync.index === index);
14499
- return matches.length === 1 ? matches[0] : undefined;
14500
- }
14501
14592
  function rpcFingerprint(values) {
14502
14593
  return values.map(({ wireHash, packetKind }) => `${wireHash}:${packetKind}`).sort().join(",");
14503
14594
  }
14504
- function definitionLookup(matches) {
14505
- const wireHashes = new Set(matches.map(({ wireHash }) => wireHash));
14506
- if (matches.length === 1 && matches[0]) {
14507
- return {
14508
- resolution: "verified",
14509
- wireHash: matches[0].wireHash,
14510
- methodName: matches[0].methodName,
14511
- parameters: matches[0].parameters
14512
- };
14513
- }
14514
- const ambiguousBehaviourTypes = matches.length > 1 ? [...new Set(matches.map(({ typeName }) => typeName).filter((typeName) => typeName !== undefined))] : undefined;
14515
- return {
14516
- resolution: matches.length > 1 ? "ambiguous" : "unresolved",
14517
- ambiguousBehaviourTypes: ambiguousBehaviourTypes?.length ? ambiguousBehaviourTypes : undefined,
14518
- wireHash: wireHashes.size === 1 ? wireHashes.values().next().value : undefined
14519
- };
14520
- }
14521
- function findPrefab(map, collectionId, prefabId) {
14522
- if (!map?.prefabs)
14523
- return;
14524
- const matches = map.prefabs.filter((layout) => layout.collectionId === collectionId && layout.prefabId === prefabId);
14525
- return matches.length === 1 ? matches[0] : undefined;
14526
- }
14527
14595
 
14528
- // src/fishnet/spawn-parser.ts
14596
+ // src/fishnet/decoding/spawn.ts
14529
14597
  function parseObjectSpawn(buffer, start, map) {
14530
14598
  try {
14531
14599
  requireBytes(buffer, start, 1, "spawn flags");
@@ -14676,7 +14744,7 @@ function parseLinkRegistrations(buffer, start, end, objectId) {
14676
14744
  return offset === end ? registrations : undefined;
14677
14745
  }
14678
14746
 
14679
- // src/fishnet/message-parser.ts
14747
+ // src/fishnet/decoding/messages.ts
14680
14748
  function parseMessage(buffer, start, tick, bundleIndex, state, options) {
14681
14749
  const packetId = buffer.readUInt16LE(start);
14682
14750
  const packetName = classifyPacket(packetId);
@@ -15005,7 +15073,7 @@ function finish(entries) {
15005
15073
  return entries.length > 0 ? entries : undefined;
15006
15074
  }
15007
15075
 
15008
- // src/fishnet/session-decoder.ts
15076
+ // src/fishnet/decoding/session.ts
15009
15077
  var MAX_SPLIT_CHUNKS = 1024;
15010
15078
  var MAX_SPLIT_BYTES = 1024 * 1024;
15011
15079
  var MAX_CONCURRENT_SPLITS_PER_CONNECTION = 32;
@@ -15181,7 +15249,7 @@ function removeObjectComponents(state, objectId) {
15181
15249
  }
15182
15250
  }
15183
15251
 
15184
- // src/fishnet/decoder.ts
15252
+ // src/fishnet/decoding/decoder.ts
15185
15253
  function decodeFishNetPayload(payload, options = { reliable: false }) {
15186
15254
  if (payload.length < 6) {
15187
15255
  throw new FishNetProtocolError(`FishNet payload needs a 4-byte tick and 2-byte packet id; received ${payload.length} bytes`);
@@ -15452,6 +15520,120 @@ function isUsable(device) {
15452
15520
  return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
15453
15521
  }
15454
15522
 
15523
+ // src/capture/duplicate-filter.ts
15524
+ var DUPLICATE_WINDOW_MS = 5;
15525
+ var DUPLICATE_ENTRY_LIMIT = 16384;
15526
+
15527
+ class DuplicateFilter {
15528
+ windowMs;
15529
+ entryLimit;
15530
+ seen = new Map;
15531
+ suppressed = 0;
15532
+ constructor(options = {}) {
15533
+ this.windowMs = options.windowMs ?? DUPLICATE_WINDOW_MS;
15534
+ this.entryLimit = options.entryLimit ?? DUPLICATE_ENTRY_LIMIT;
15535
+ }
15536
+ get suppressedCount() {
15537
+ return this.suppressed;
15538
+ }
15539
+ admit(packet, nowMs = Date.now()) {
15540
+ if (packet.payload.length === 0)
15541
+ return true;
15542
+ const key = digest(packet.protocol, packet.direction, localPortOf(packet), packet.payload);
15543
+ const previous = this.seen.get(key);
15544
+ if (previous !== undefined && nowMs - previous < this.windowMs) {
15545
+ this.suppressed += 1;
15546
+ return false;
15547
+ }
15548
+ this.seen.delete(key);
15549
+ this.seen.set(key, nowMs);
15550
+ this.evict(nowMs);
15551
+ return true;
15552
+ }
15553
+ takeSuppressedCount() {
15554
+ const total = this.suppressed;
15555
+ this.suppressed = 0;
15556
+ return total;
15557
+ }
15558
+ reset() {
15559
+ this.seen.clear();
15560
+ this.suppressed = 0;
15561
+ }
15562
+ evict(nowMs) {
15563
+ const cutoff = nowMs - this.windowMs;
15564
+ for (const [key, observedAt] of this.seen) {
15565
+ if (observedAt >= cutoff && this.seen.size <= this.entryLimit)
15566
+ break;
15567
+ this.seen.delete(key);
15568
+ }
15569
+ }
15570
+ }
15571
+ function digest(protocol, direction, localPort, payload) {
15572
+ return `${protocol}:${direction}:${localPort}:${payload.toString("latin1")}`;
15573
+ }
15574
+ function localPortOf(packet) {
15575
+ return packet.direction === "outbound" ? packet.sourcePort : packet.destinationPort;
15576
+ }
15577
+
15578
+ // src/capture/drop-diagnostics.ts
15579
+ var DROP_FLOW_LIMIT = 64;
15580
+ var DROP_FLOW_REPORT_COUNT = 5;
15581
+ var DROP_VERDICT_MIN_SAMPLES = 20;
15582
+ var DROP_VERDICT_THRESHOLD = 0.25;
15583
+
15584
+ class DropDiagnostics {
15585
+ flows = new Map;
15586
+ record(packet) {
15587
+ const flow = flowName(packet);
15588
+ let stats = this.flows.get(flow);
15589
+ if (!stats) {
15590
+ if (this.flows.size >= DROP_FLOW_LIMIT)
15591
+ return;
15592
+ stats = { packets: 0, sequenced: 0, consecutive: 0 };
15593
+ this.flows.set(flow, stats);
15594
+ }
15595
+ stats.packets += 1;
15596
+ this.countSequence(stats, packet);
15597
+ }
15598
+ topFlows(limit = DROP_FLOW_REPORT_COUNT) {
15599
+ return [...this.flows].sort(([, a], [, b]) => b.packets - a.packets).slice(0, limit).map(([flow, stats]) => ({ flow, packets: stats.packets, verdict: verdictFor(stats) }));
15600
+ }
15601
+ get hasGameTraffic() {
15602
+ return [...this.flows.values()].some((stats) => verdictFor(stats) === "game traffic");
15603
+ }
15604
+ reset() {
15605
+ this.flows.clear();
15606
+ }
15607
+ countSequence(stats, packet) {
15608
+ if (packet.protocol !== "udp" || packet.payload.length === 0)
15609
+ return;
15610
+ let decoded;
15611
+ try {
15612
+ decoded = decodeLiteNetLibDatagram(packet.payload);
15613
+ } catch {
15614
+ return;
15615
+ }
15616
+ for (const { packet: inner } of decoded) {
15617
+ const sequence = "sequence" in inner ? inner.sequence : undefined;
15618
+ if (sequence === undefined)
15619
+ continue;
15620
+ stats.sequenced += 1;
15621
+ if (stats.previous !== undefined && sequence === (stats.previous + 1) % 32768) {
15622
+ stats.consecutive += 1;
15623
+ }
15624
+ stats.previous = sequence;
15625
+ }
15626
+ }
15627
+ }
15628
+ function verdictFor(stats) {
15629
+ if (stats.sequenced < DROP_VERDICT_MIN_SAMPLES)
15630
+ return "unknown";
15631
+ return stats.consecutive / stats.sequenced >= DROP_VERDICT_THRESHOLD ? "game traffic" : "unrelated";
15632
+ }
15633
+ function flowName(packet) {
15634
+ return `${packet.protocol} ${packet.sourceIP}:${packet.sourcePort} -> ${packet.destinationIP}:${packet.destinationPort}`;
15635
+ }
15636
+
15455
15637
  // src/capture/link-layer.ts
15456
15638
  var DATA_LINK = {
15457
15639
  NULL: 0,
@@ -15928,6 +16110,7 @@ var MAX_POLL_BATCH = 128;
15928
16110
  var PENDING_PACKET_LIMIT = 16384;
15929
16111
  var PENDING_PACKET_MAX_AGE_MS = TARGET_REFRESH_INTERVAL_MS * 5;
15930
16112
  var PENDING_DROP_REPORT_MS = 1e4;
16113
+ var DUPLICATE_REPORT_MS = 1e4;
15931
16114
  var systemRuntime = new SystemNpcapRuntime;
15932
16115
  async function getNpcapStatus() {
15933
16116
  return systemRuntime.status();
@@ -15952,6 +16135,11 @@ class PacketCapture extends EventEmitter {
15952
16135
  currentConnectionId;
15953
16136
  droppedPending = 0;
15954
16137
  droppedReportedAtMs = 0;
16138
+ duplicateFilter = null;
16139
+ duplicateReportedAtMs = 0;
16140
+ relayObserved = false;
16141
+ dropDiagnostics = new DropDiagnostics;
16142
+ deviceLabel = "";
15955
16143
  _state = "stopped";
15956
16144
  constructor(dependencies = {}) {
15957
16145
  super();
@@ -15987,10 +16175,12 @@ class PacketCapture extends EventEmitter {
15987
16175
  this.decodeLiteNetLib = (config.decodeLiteNetLib ?? false) || decodeFishNet;
15988
16176
  this.fishNetRpcMap = decodeFishNet ? config.fishNetRpcMap ?? loadBundledFishNetRpcMap(config.fishNetBuildFingerprint) : undefined;
15989
16177
  this.fishNetSessionDecoder = decodeFishNet ? new FishNetSessionDecoder(this.fishNetRpcMap) : null;
16178
+ this.duplicateFilter = config.suppressDuplicates ?? true ? new DuplicateFilter : null;
15990
16179
  const devices = await this.runtime.listDevices();
15991
16180
  const resolved = await resolveCaptureDevice(devices, config.deviceName);
15992
16181
  if (!resolved.device)
15993
16182
  throw new Error("Npcap did not report a usable network adapter");
16183
+ this.deviceLabel = resolved.device.description || resolved.device.name;
15994
16184
  const filter = config.filter ?? Array.from(new Set(protocols)).join(" or ");
15995
16185
  this.session = await this.runtime.open(resolved.device, filter);
15996
16186
  if (!supportsDataLink(this.session.dataLink)) {
@@ -16027,6 +16217,7 @@ class PacketCapture extends EventEmitter {
16027
16217
  this.polling = true;
16028
16218
  try {
16029
16219
  this.flushPending();
16220
+ this.reportSuppressedDuplicates();
16030
16221
  for (let index = 0;index < MAX_POLL_BATCH; index += 1) {
16031
16222
  const captured = this.session.nextPacket();
16032
16223
  if (!captured)
@@ -16054,7 +16245,7 @@ class PacketCapture extends EventEmitter {
16054
16245
  this.emitTransportPacket(packet);
16055
16246
  } else {
16056
16247
  if (this.pending.length >= PENDING_PACKET_LIMIT)
16057
- this.droppedPending += 1;
16248
+ this.recordDropped(packet);
16058
16249
  else
16059
16250
  this.pending.push({ packet, observedAt: Date.now() });
16060
16251
  }
@@ -16071,6 +16262,10 @@ class PacketCapture extends EventEmitter {
16071
16262
  this.polling = false;
16072
16263
  }
16073
16264
  }
16265
+ recordDropped(packet) {
16266
+ this.droppedPending += 1;
16267
+ this.dropDiagnostics.record(packet);
16268
+ }
16074
16269
  flushPending() {
16075
16270
  if (!this.target)
16076
16271
  return;
@@ -16082,7 +16277,7 @@ class PacketCapture extends EventEmitter {
16082
16277
  const remaining = [];
16083
16278
  for (const candidate of this.pending) {
16084
16279
  if (candidate.observedAt < cutoff) {
16085
- this.droppedPending += 1;
16280
+ this.recordDropped(candidate.packet);
16086
16281
  continue;
16087
16282
  }
16088
16283
  const direction = this.target.classify(candidate.packet);
@@ -16096,6 +16291,17 @@ class PacketCapture extends EventEmitter {
16096
16291
  this.pending = remaining;
16097
16292
  this.reportDroppedPending();
16098
16293
  }
16294
+ reportSuppressedDuplicates() {
16295
+ if (!this.duplicateFilter || this.duplicateFilter.suppressedCount === 0)
16296
+ return;
16297
+ const now = Date.now();
16298
+ this.duplicateReportedAtMs ||= now;
16299
+ if (now - this.duplicateReportedAtMs < DUPLICATE_REPORT_MS)
16300
+ return;
16301
+ const total = this.duplicateFilter.takeSuppressedCount();
16302
+ this.emitSafely("warning", `suppressed ${total} duplicate packets on ${this.deviceLabel}; a VPN or proxy is relaying this traffic` + ", so select the adapter carrying it directly if decoding looks incomplete");
16303
+ this.duplicateReportedAtMs = now;
16304
+ }
16099
16305
  reportDroppedPending() {
16100
16306
  if (this.droppedPending === 0)
16101
16307
  return;
@@ -16103,11 +16309,20 @@ class PacketCapture extends EventEmitter {
16103
16309
  this.droppedReportedAtMs ||= now;
16104
16310
  if (now - this.droppedReportedAtMs < PENDING_DROP_REPORT_MS)
16105
16311
  return;
16106
- this.emitSafely("warning", `gave up on ${this.droppedPending} packets that could not be attributed to the target process`);
16312
+ const relay = this.relayObserved ? "; a VPN or proxy is also relaying traffic on this adapter" : "";
16313
+ this.emitSafely("warning", `gave up on ${this.droppedPending} packets that could not be attributed to the target process${relay}`);
16314
+ this.emitSafely("droppedFlows", this.dropDiagnostics.topFlows());
16315
+ if (this.dropDiagnostics.hasGameTraffic) {
16316
+ this.emitSafely("warning", "some discarded traffic carries sequenced game packets, so the target process does not own" + " every socket its traffic uses; capture that adapter directly or report these flows");
16317
+ }
16107
16318
  this.droppedPending = 0;
16108
16319
  this.droppedReportedAtMs = now;
16109
16320
  }
16110
16321
  emitTransportPacket(packet) {
16322
+ if (this.duplicateFilter && !this.duplicateFilter.admit(packet)) {
16323
+ this.relayObserved = true;
16324
+ return;
16325
+ }
16111
16326
  if (packet.protocol === "tcp")
16112
16327
  this.emitSafely("packet", packet);
16113
16328
  else
@@ -16207,12 +16422,18 @@ class PacketCapture extends EventEmitter {
16207
16422
  this.currentConnectionId = undefined;
16208
16423
  this.droppedPending = 0;
16209
16424
  this.droppedReportedAtMs = 0;
16425
+ this.duplicateFilter?.reset();
16426
+ this.duplicateFilter = null;
16427
+ this.duplicateReportedAtMs = 0;
16428
+ this.relayObserved = false;
16429
+ this.dropDiagnostics.reset();
16430
+ this.deviceLabel = "";
16210
16431
  }
16211
16432
  }
16212
16433
  function connectionIdFor(packet) {
16213
16434
  const udp = packet.udpPacket;
16214
- const endpoints = [`${udp.sourceIP}:${udp.sourcePort}`, `${udp.destinationIP}:${udp.destinationPort}`].sort();
16215
- return `${endpoints[0]}<->${endpoints[1]}#${packet.packet.connectionNumber}`;
16435
+ const localPort = udp.direction === "outbound" ? udp.sourcePort : udp.destinationPort;
16436
+ return `local:${localPort}#${packet.packet.connectionNumber}`;
16216
16437
  }
16217
16438
  function inferDirection(ipPacket, device) {
16218
16439
  if (ipPacket[0] >> 4 === 4 && ipPacket.length >= 20) {