@kar-mi/spirit-vale-tools-capture 2.5.1 → 2.7.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.
@@ -0,0 +1,42 @@
1
+ import type { CapturedTransportPacket } from "../types.ts";
2
+ /** Flows tracked before new ones are ignored. */
3
+ export declare const DROP_FLOW_LIMIT = 64;
4
+ /** Flows named in a summary. */
5
+ export declare const DROP_FLOW_REPORT_COUNT = 5;
6
+ /** Sequenced packets a flow must show before its verdict is trusted. */
7
+ export declare const DROP_VERDICT_MIN_SAMPLES = 20;
8
+ /**
9
+ * Fraction of consecutive sequence numbers above which a flow is called game traffic. A real stream
10
+ * sits far above this and traffic that merely parses as LiteNetLib far below, so it is not delicate.
11
+ */
12
+ export declare const DROP_VERDICT_THRESHOLD = 0.25;
13
+ export interface DroppedFlow {
14
+ flow: string;
15
+ packets: number;
16
+ verdict: "game traffic" | "unrelated" | "unknown";
17
+ }
18
+ /**
19
+ * Explains what unattributed capture is throwing away, without keeping any of it.
20
+ *
21
+ * A high give-up count reads as lost game traffic and usually is not. Per flow this keeps a count
22
+ * and one statistic - how often a LiteNetLib sequence number follows its predecessor - which tells
23
+ * a real stream from traffic that merely parses as one, since nothing else maintains a sequence.
24
+ * No payload is retained, so a summary is safe to share.
25
+ */
26
+ export declare class DropDiagnostics {
27
+ private readonly flows;
28
+ /** Records a packet that could not be attributed and was given up on. */
29
+ record(packet: CapturedTransportPacket): void;
30
+ /**
31
+ * The busiest dropped flows, worst first, each with a verdict.
32
+ *
33
+ * Counts run for the whole session rather than since the last call, because a verdict needs
34
+ * accumulated sequence samples to settle. Callers reporting a windowed count should say so.
35
+ */
36
+ topFlows(limit?: number): DroppedFlow[];
37
+ /** Whether any dropped flow looks like game traffic - the only case worth acting on. */
38
+ get hasGameTraffic(): boolean;
39
+ reset(): void;
40
+ private countSequence;
41
+ }
42
+ //# sourceMappingURL=drop-diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drop-diagnostics.d.ts","sourceRoot":"","sources":["../../src/capture/drop-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,iDAAiD;AACjD,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,gCAAgC;AAChC,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAS3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,cAAc,GAAG,WAAW,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IAEtD,yEAAyE;IACzE,MAAM,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAU5C;IAED;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,SAAyB,GAAG,WAAW,EAAE,CAKtD;IAED,wFAAwF;IACxF,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,OAAO,CAAC,aAAa;CAoBtB"}
@@ -0,0 +1,39 @@
1
+ import type { CapturedTransportPacket } from "../types.ts";
2
+ /** How long a payload stays remembered. Relayed copies arrive within about a millisecond. */
3
+ export declare const DUPLICATE_WINDOW_MS = 5;
4
+ /** Entries retained before the oldest are evicted, mirroring the pending-packet ceiling. */
5
+ export declare const DUPLICATE_ENTRY_LIMIT = 16384;
6
+ export interface DuplicateFilterOptions {
7
+ windowMs?: number;
8
+ entryLimit?: number;
9
+ }
10
+ /**
11
+ * Suppresses the redundant copies a redirecting VPN puts on the wire.
12
+ *
13
+ * Relays such as ExitLag rewrite addresses and ports rather than encapsulating, and send a datagram
14
+ * over several routes at once, so one datagram is captured more than once. The key is the payload
15
+ * with the direction and local port, which a relay preserves, never the peer's endpoint, which it
16
+ * rewrites. Direction keeps a peer's echo of an identical control payload distinct; the local port
17
+ * keeps two sockets distinct when they send the same bytes at once, as two connections opening
18
+ * together do.
19
+ */
20
+ export declare class DuplicateFilter {
21
+ private readonly windowMs;
22
+ private readonly entryLimit;
23
+ private readonly seen;
24
+ private suppressed;
25
+ constructor(options?: DuplicateFilterOptions);
26
+ /** Total copies suppressed since the last {@link takeSuppressedCount}. */
27
+ get suppressedCount(): number;
28
+ /**
29
+ * Reports whether a packet is the first sighting of its payload and should be decoded.
30
+ *
31
+ * Payload-free packets are always admitted; they carry nothing to tell copies apart.
32
+ */
33
+ admit(packet: CapturedTransportPacket, nowMs?: number): boolean;
34
+ /** Returns the suppressed count and resets it, for periodic reporting. */
35
+ takeSuppressedCount(): number;
36
+ reset(): void;
37
+ private evict;
38
+ }
39
+ //# sourceMappingURL=duplicate-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-filter.d.ts","sourceRoot":"","sources":["../../src/capture/duplicate-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE5E,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,4FAA4F;AAC5F,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAK;IAEvB,YAAY,OAAO,GAAE,sBAA2B,EAG/C;IAED,0EAA0E;IAC1E,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,GAAE,MAAmB,GAAG,OAAO,CAa1E;IAED,0EAA0E;IAC1E,mBAAmB,IAAI,MAAM,CAI5B;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,CAAC,KAAK;CAQd"}
@@ -1,9 +1,10 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import type { CapturedLiteNetLibPacket } from "../litenetlib/types.ts";
3
3
  import type { CapturedFishNetPacket } from "../fishnet/types.ts";
4
+ import type { DroppedFlow } from "./drop-diagnostics.ts";
4
5
  import type { NpcapDevice, NpcapRuntime, NpcapStatus } from "./npcap.ts";
5
6
  import type { TargetSnapshotProvider } from "./target-tracker.ts";
6
- import type { CaptureConfig, CaptureTargetStatus, CapturedTcpPacket, CapturedTransportPacket, CapturedUdpPacket, CaptureState } from "../types.ts";
7
+ import type { CaptureConfig, CaptureConnectionEvent, CaptureTargetStatus, CapturedTcpPacket, CapturedTransportPacket, CapturedUdpPacket, CaptureState } from "../types.ts";
7
8
  export interface PacketCaptureDependencies {
8
9
  runtime?: NpcapRuntime;
9
10
  targetProvider?: TargetSnapshotProvider;
@@ -24,29 +25,51 @@ export declare class PacketCapture extends EventEmitter {
24
25
  private decodeFishNet;
25
26
  private fishNetRpcMap;
26
27
  private fishNetSessionDecoder;
28
+ private currentConnectionId?;
27
29
  private droppedPending;
28
30
  private droppedReportedAtMs;
31
+ private duplicateFilter;
32
+ private duplicateReportedAtMs;
33
+ private relayObserved;
34
+ private readonly dropDiagnostics;
35
+ private deviceLabel;
29
36
  private _state;
30
37
  constructor(dependencies?: PacketCaptureDependencies);
31
38
  get state(): CaptureState;
39
+ /** The connection last seen opening and not since closing, undefined until one is observed. */
40
+ get connectionId(): string | undefined;
32
41
  on(event: "started", listener: () => void): this;
33
42
  on(event: "packet", listener: (packet: CapturedTcpPacket) => void): this;
34
43
  on(event: "udpPacket", listener: (packet: CapturedUdpPacket) => void): this;
35
44
  on(event: "transportPacket", listener: (packet: CapturedTransportPacket) => void): this;
36
45
  on(event: "liteNetPacket", listener: (packet: CapturedLiteNetLibPacket) => void): this;
37
46
  on(event: "fishNetPacket", listener: (packet: CapturedFishNetPacket) => void): this;
47
+ on(event: "connection", listener: (event: CaptureConnectionEvent) => void): this;
38
48
  on(event: "targetStatus", listener: (status: CaptureTargetStatus) => void): this;
49
+ on(event: "droppedFlows", listener: (flows: DroppedFlow[]) => void): this;
39
50
  on(event: "warning", listener: (message: string) => void): this;
40
51
  on(event: "error", listener: (error: Error) => void): this;
41
52
  on(event: "stopped", listener: () => void): this;
42
53
  start(config?: CaptureConfig): Promise<void>;
43
54
  stop(): Promise<void>;
44
55
  private poll;
56
+ /** Counts a packet given up on and folds it into the drop diagnostics. */
57
+ private recordDropped;
45
58
  private flushPending;
59
+ /** Reports copies a relay put on the wire, naming the adapter in case a better one exists. */
60
+ private reportSuppressedDuplicates;
46
61
  /** Reports packets given up on unattributed, which is how a connection loses its opening. */
47
62
  private reportDroppedPending;
48
63
  private emitTransportPacket;
49
64
  private emitLiteNetLibPackets;
65
+ /**
66
+ * Follows the connection the game is playing on.
67
+ *
68
+ * FishNet says so once per connection, in `authenticated`; a consumer that misses that packet
69
+ * stays pinned to a connection the game has left, discarding everything the live one sends.
70
+ * Connects and disconnects are announced continually, so losing one of those costs nothing.
71
+ */
72
+ private trackConnection;
50
73
  private emitFishNetPacket;
51
74
  private emitSafely;
52
75
  private closeResources;
@@ -1 +1 @@
1
- {"version":3,"file":"packet-capture.d.ts","sourceRoot":"","sources":["../../src/capture/packet-capture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"packet-capture.d.ts","sourceRoot":"","sources":["../../src/capture/packet-capture.ts"],"names":[],"mappings":"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"}
package/dist/capture.js CHANGED
@@ -15452,6 +15452,120 @@ function isUsable(device) {
15452
15452
  return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
15453
15453
  }
15454
15454
 
15455
+ // src/capture/duplicate-filter.ts
15456
+ var DUPLICATE_WINDOW_MS = 5;
15457
+ var DUPLICATE_ENTRY_LIMIT = 16384;
15458
+
15459
+ class DuplicateFilter {
15460
+ windowMs;
15461
+ entryLimit;
15462
+ seen = new Map;
15463
+ suppressed = 0;
15464
+ constructor(options = {}) {
15465
+ this.windowMs = options.windowMs ?? DUPLICATE_WINDOW_MS;
15466
+ this.entryLimit = options.entryLimit ?? DUPLICATE_ENTRY_LIMIT;
15467
+ }
15468
+ get suppressedCount() {
15469
+ return this.suppressed;
15470
+ }
15471
+ admit(packet, nowMs = Date.now()) {
15472
+ if (packet.payload.length === 0)
15473
+ return true;
15474
+ const key = digest(packet.protocol, packet.direction, localPortOf(packet), packet.payload);
15475
+ const previous = this.seen.get(key);
15476
+ if (previous !== undefined && nowMs - previous < this.windowMs) {
15477
+ this.suppressed += 1;
15478
+ return false;
15479
+ }
15480
+ this.seen.delete(key);
15481
+ this.seen.set(key, nowMs);
15482
+ this.evict(nowMs);
15483
+ return true;
15484
+ }
15485
+ takeSuppressedCount() {
15486
+ const total = this.suppressed;
15487
+ this.suppressed = 0;
15488
+ return total;
15489
+ }
15490
+ reset() {
15491
+ this.seen.clear();
15492
+ this.suppressed = 0;
15493
+ }
15494
+ evict(nowMs) {
15495
+ const cutoff = nowMs - this.windowMs;
15496
+ for (const [key, observedAt] of this.seen) {
15497
+ if (observedAt >= cutoff && this.seen.size <= this.entryLimit)
15498
+ break;
15499
+ this.seen.delete(key);
15500
+ }
15501
+ }
15502
+ }
15503
+ function digest(protocol, direction, localPort, payload) {
15504
+ return `${protocol}:${direction}:${localPort}:${payload.toString("latin1")}`;
15505
+ }
15506
+ function localPortOf(packet) {
15507
+ return packet.direction === "outbound" ? packet.sourcePort : packet.destinationPort;
15508
+ }
15509
+
15510
+ // src/capture/drop-diagnostics.ts
15511
+ var DROP_FLOW_LIMIT = 64;
15512
+ var DROP_FLOW_REPORT_COUNT = 5;
15513
+ var DROP_VERDICT_MIN_SAMPLES = 20;
15514
+ var DROP_VERDICT_THRESHOLD = 0.25;
15515
+
15516
+ class DropDiagnostics {
15517
+ flows = new Map;
15518
+ record(packet) {
15519
+ const flow = flowName(packet);
15520
+ let stats = this.flows.get(flow);
15521
+ if (!stats) {
15522
+ if (this.flows.size >= DROP_FLOW_LIMIT)
15523
+ return;
15524
+ stats = { packets: 0, sequenced: 0, consecutive: 0 };
15525
+ this.flows.set(flow, stats);
15526
+ }
15527
+ stats.packets += 1;
15528
+ this.countSequence(stats, packet);
15529
+ }
15530
+ topFlows(limit = DROP_FLOW_REPORT_COUNT) {
15531
+ return [...this.flows].sort(([, a], [, b]) => b.packets - a.packets).slice(0, limit).map(([flow, stats]) => ({ flow, packets: stats.packets, verdict: verdictFor(stats) }));
15532
+ }
15533
+ get hasGameTraffic() {
15534
+ return [...this.flows.values()].some((stats) => verdictFor(stats) === "game traffic");
15535
+ }
15536
+ reset() {
15537
+ this.flows.clear();
15538
+ }
15539
+ countSequence(stats, packet) {
15540
+ if (packet.protocol !== "udp" || packet.payload.length === 0)
15541
+ return;
15542
+ let decoded;
15543
+ try {
15544
+ decoded = decodeLiteNetLibDatagram(packet.payload);
15545
+ } catch {
15546
+ return;
15547
+ }
15548
+ for (const { packet: inner } of decoded) {
15549
+ const sequence = "sequence" in inner ? inner.sequence : undefined;
15550
+ if (sequence === undefined)
15551
+ continue;
15552
+ stats.sequenced += 1;
15553
+ if (stats.previous !== undefined && sequence === (stats.previous + 1) % 32768) {
15554
+ stats.consecutive += 1;
15555
+ }
15556
+ stats.previous = sequence;
15557
+ }
15558
+ }
15559
+ }
15560
+ function verdictFor(stats) {
15561
+ if (stats.sequenced < DROP_VERDICT_MIN_SAMPLES)
15562
+ return "unknown";
15563
+ return stats.consecutive / stats.sequenced >= DROP_VERDICT_THRESHOLD ? "game traffic" : "unrelated";
15564
+ }
15565
+ function flowName(packet) {
15566
+ return `${packet.protocol} ${packet.sourceIP}:${packet.sourcePort} -> ${packet.destinationIP}:${packet.destinationPort}`;
15567
+ }
15568
+
15455
15569
  // src/capture/link-layer.ts
15456
15570
  var DATA_LINK = {
15457
15571
  NULL: 0,
@@ -15928,6 +16042,7 @@ var MAX_POLL_BATCH = 128;
15928
16042
  var PENDING_PACKET_LIMIT = 16384;
15929
16043
  var PENDING_PACKET_MAX_AGE_MS = TARGET_REFRESH_INTERVAL_MS * 5;
15930
16044
  var PENDING_DROP_REPORT_MS = 1e4;
16045
+ var DUPLICATE_REPORT_MS = 1e4;
15931
16046
  var systemRuntime = new SystemNpcapRuntime;
15932
16047
  async function getNpcapStatus() {
15933
16048
  return systemRuntime.status();
@@ -15949,8 +16064,14 @@ class PacketCapture extends EventEmitter {
15949
16064
  decodeFishNet = false;
15950
16065
  fishNetRpcMap;
15951
16066
  fishNetSessionDecoder = null;
16067
+ currentConnectionId;
15952
16068
  droppedPending = 0;
15953
16069
  droppedReportedAtMs = 0;
16070
+ duplicateFilter = null;
16071
+ duplicateReportedAtMs = 0;
16072
+ relayObserved = false;
16073
+ dropDiagnostics = new DropDiagnostics;
16074
+ deviceLabel = "";
15954
16075
  _state = "stopped";
15955
16076
  constructor(dependencies = {}) {
15956
16077
  super();
@@ -15961,6 +16082,9 @@ class PacketCapture extends EventEmitter {
15961
16082
  get state() {
15962
16083
  return this._state;
15963
16084
  }
16085
+ get connectionId() {
16086
+ return this.currentConnectionId;
16087
+ }
15964
16088
  on(event, listener) {
15965
16089
  return super.on(event, listener);
15966
16090
  }
@@ -15983,10 +16107,12 @@ class PacketCapture extends EventEmitter {
15983
16107
  this.decodeLiteNetLib = (config.decodeLiteNetLib ?? false) || decodeFishNet;
15984
16108
  this.fishNetRpcMap = decodeFishNet ? config.fishNetRpcMap ?? loadBundledFishNetRpcMap(config.fishNetBuildFingerprint) : undefined;
15985
16109
  this.fishNetSessionDecoder = decodeFishNet ? new FishNetSessionDecoder(this.fishNetRpcMap) : null;
16110
+ this.duplicateFilter = config.suppressDuplicates ?? true ? new DuplicateFilter : null;
15986
16111
  const devices = await this.runtime.listDevices();
15987
16112
  const resolved = await resolveCaptureDevice(devices, config.deviceName);
15988
16113
  if (!resolved.device)
15989
16114
  throw new Error("Npcap did not report a usable network adapter");
16115
+ this.deviceLabel = resolved.device.description || resolved.device.name;
15990
16116
  const filter = config.filter ?? Array.from(new Set(protocols)).join(" or ");
15991
16117
  this.session = await this.runtime.open(resolved.device, filter);
15992
16118
  if (!supportsDataLink(this.session.dataLink)) {
@@ -16023,6 +16149,7 @@ class PacketCapture extends EventEmitter {
16023
16149
  this.polling = true;
16024
16150
  try {
16025
16151
  this.flushPending();
16152
+ this.reportSuppressedDuplicates();
16026
16153
  for (let index = 0;index < MAX_POLL_BATCH; index += 1) {
16027
16154
  const captured = this.session.nextPacket();
16028
16155
  if (!captured)
@@ -16050,7 +16177,7 @@ class PacketCapture extends EventEmitter {
16050
16177
  this.emitTransportPacket(packet);
16051
16178
  } else {
16052
16179
  if (this.pending.length >= PENDING_PACKET_LIMIT)
16053
- this.droppedPending += 1;
16180
+ this.recordDropped(packet);
16054
16181
  else
16055
16182
  this.pending.push({ packet, observedAt: Date.now() });
16056
16183
  }
@@ -16067,6 +16194,10 @@ class PacketCapture extends EventEmitter {
16067
16194
  this.polling = false;
16068
16195
  }
16069
16196
  }
16197
+ recordDropped(packet) {
16198
+ this.droppedPending += 1;
16199
+ this.dropDiagnostics.record(packet);
16200
+ }
16070
16201
  flushPending() {
16071
16202
  if (!this.target)
16072
16203
  return;
@@ -16078,7 +16209,7 @@ class PacketCapture extends EventEmitter {
16078
16209
  const remaining = [];
16079
16210
  for (const candidate of this.pending) {
16080
16211
  if (candidate.observedAt < cutoff) {
16081
- this.droppedPending += 1;
16212
+ this.recordDropped(candidate.packet);
16082
16213
  continue;
16083
16214
  }
16084
16215
  const direction = this.target.classify(candidate.packet);
@@ -16092,6 +16223,17 @@ class PacketCapture extends EventEmitter {
16092
16223
  this.pending = remaining;
16093
16224
  this.reportDroppedPending();
16094
16225
  }
16226
+ reportSuppressedDuplicates() {
16227
+ if (!this.duplicateFilter || this.duplicateFilter.suppressedCount === 0)
16228
+ return;
16229
+ const now = Date.now();
16230
+ this.duplicateReportedAtMs ||= now;
16231
+ if (now - this.duplicateReportedAtMs < DUPLICATE_REPORT_MS)
16232
+ return;
16233
+ const total = this.duplicateFilter.takeSuppressedCount();
16234
+ 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");
16235
+ this.duplicateReportedAtMs = now;
16236
+ }
16095
16237
  reportDroppedPending() {
16096
16238
  if (this.droppedPending === 0)
16097
16239
  return;
@@ -16099,11 +16241,20 @@ class PacketCapture extends EventEmitter {
16099
16241
  this.droppedReportedAtMs ||= now;
16100
16242
  if (now - this.droppedReportedAtMs < PENDING_DROP_REPORT_MS)
16101
16243
  return;
16102
- this.emitSafely("warning", `gave up on ${this.droppedPending} packets that could not be attributed to the target process`);
16244
+ const relay = this.relayObserved ? "; a VPN or proxy is also relaying traffic on this adapter" : "";
16245
+ this.emitSafely("warning", `gave up on ${this.droppedPending} packets that could not be attributed to the target process${relay}`);
16246
+ this.emitSafely("droppedFlows", this.dropDiagnostics.topFlows());
16247
+ if (this.dropDiagnostics.hasGameTraffic) {
16248
+ 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");
16249
+ }
16103
16250
  this.droppedPending = 0;
16104
16251
  this.droppedReportedAtMs = now;
16105
16252
  }
16106
16253
  emitTransportPacket(packet) {
16254
+ if (this.duplicateFilter && !this.duplicateFilter.admit(packet)) {
16255
+ this.relayObserved = true;
16256
+ return;
16257
+ }
16107
16258
  if (packet.protocol === "tcp")
16108
16259
  this.emitSafely("packet", packet);
16109
16260
  else
@@ -16117,6 +16268,7 @@ class PacketCapture extends EventEmitter {
16117
16268
  for (const decoded of decodeLiteNetLibDatagram(packet.payload)) {
16118
16269
  const captured = { ...decoded, udpPacket: packet };
16119
16270
  this.emitSafely("liteNetPacket", captured);
16271
+ this.trackConnection(captured);
16120
16272
  if (this.decodeFishNet)
16121
16273
  this.emitFishNetPacket(captured);
16122
16274
  }
@@ -16125,11 +16277,26 @@ class PacketCapture extends EventEmitter {
16125
16277
  this.emitSafely("warning", `skipped LiteNetLib decode: ${detail}`);
16126
16278
  }
16127
16279
  }
16280
+ trackConnection(packet) {
16281
+ const { property } = packet.packet;
16282
+ if (property !== "connectRequest" && property !== "connectAccept" && property !== "disconnect")
16283
+ return;
16284
+ const connectionId = connectionIdFor(packet);
16285
+ if (property === "disconnect") {
16286
+ if (this.currentConnectionId === connectionId)
16287
+ this.currentConnectionId = undefined;
16288
+ this.emitSafely("connection", { connectionId, state: "closed" });
16289
+ return;
16290
+ }
16291
+ if (this.currentConnectionId === connectionId)
16292
+ return;
16293
+ this.currentConnectionId = connectionId;
16294
+ this.emitSafely("connection", { connectionId, state: "opened" });
16295
+ }
16128
16296
  emitFishNetPacket(packet) {
16129
16297
  const { property, payload } = packet.packet;
16130
16298
  const udp = packet.udpPacket;
16131
- const endpoints = [`${udp.sourceIP}:${udp.sourcePort}`, `${udp.destinationIP}:${udp.destinationPort}`].sort();
16132
- const connectionId = `${endpoints[0]}<->${endpoints[1]}#${packet.packet.connectionNumber}`;
16299
+ const connectionId = connectionIdFor(packet);
16133
16300
  if (property === "connectRequest" || property === "connectAccept" || property === "disconnect") {
16134
16301
  this.fishNetSessionDecoder?.reset(connectionId);
16135
16302
  return;
@@ -16184,10 +16351,22 @@ class PacketCapture extends EventEmitter {
16184
16351
  this.fishNetRpcMap = undefined;
16185
16352
  this.fishNetSessionDecoder?.reset();
16186
16353
  this.fishNetSessionDecoder = null;
16354
+ this.currentConnectionId = undefined;
16187
16355
  this.droppedPending = 0;
16188
16356
  this.droppedReportedAtMs = 0;
16357
+ this.duplicateFilter?.reset();
16358
+ this.duplicateFilter = null;
16359
+ this.duplicateReportedAtMs = 0;
16360
+ this.relayObserved = false;
16361
+ this.dropDiagnostics.reset();
16362
+ this.deviceLabel = "";
16189
16363
  }
16190
16364
  }
16365
+ function connectionIdFor(packet) {
16366
+ const udp = packet.udpPacket;
16367
+ const localPort = udp.direction === "outbound" ? udp.sourcePort : udp.destinationPort;
16368
+ return `local:${localPort}#${packet.packet.connectionNumber}`;
16369
+ }
16191
16370
  function inferDirection(ipPacket, device) {
16192
16371
  if (ipPacket[0] >> 4 === 4 && ipPacket.length >= 20) {
16193
16372
  const source = `${ipPacket[12]}.${ipPacket[13]}.${ipPacket[14]}.${ipPacket[15]}`;
package/dist/index.d.ts CHANGED
@@ -20,7 +20,8 @@ export { FishNetMonsterDirectory, decodeMonsterSpawn, } from "./fishnet/monster-
20
20
  export type { FishNetMonsterDirectoryChange, FishNetMonsterLevels, FishNetMonsterSpawn, } from "./fishnet/monster-directory.ts";
21
21
  export { decodeBossGravestone } from "./fishnet/boss-gravestone.ts";
22
22
  export type { BossGravestone } from "./fishnet/boss-gravestone.ts";
23
- export type { CaptureConfig, CaptureProtocol, CaptureState, CaptureTargetStatus, CapturedTcpPacket, CapturedTransportPacket, CapturedUdpPacket, } from "./types.ts";
23
+ export type { DroppedFlow } from "./capture/drop-diagnostics.ts";
24
+ export type { CaptureConfig, CaptureConnectionEvent, CaptureProtocol, CaptureState, CaptureTargetStatus, CapturedTcpPacket, CapturedTransportPacket, CapturedUdpPacket, } from "./types.ts";
24
25
  export type { CapturedLiteNetLibPacket, DecodedLiteNetLibPacket, LiteNetLibAckPacket, LiteNetLibChanneledPacket, LiteNetLibControlPacket, LiteNetLibFragment, LiteNetLibPacket, LiteNetLibPacketProperty, LiteNetLibPingPacket, LiteNetLibPongPacket, LiteNetLibUnreliablePacket, } from "./litenetlib/types.ts";
25
26
  export type { CapturedFishNetPacket, DecodedFishNetPacket, FishNetDecodeOptions, FishNetDecodedField, FishNetDecodedValue, FishNetBehaviourDefinition, FishNetBroadcastDefinition, FishNetPrefabComponentDefinition, FishNetPrefabDefinition, FishNetPacketName, FishNetRpcMap, FishNetRpcPacketName, FishNetRpcDefinition, FishNetRpcLinkRegistration, FishNetRpcParameter, FishNetRpcResolution, FishNetSpawnSyncEntry, FishNetSyncEntry, FishNetSyncTypeDefinition, FishNetWireCodec, DecodedNetworkTransform, NetworkTransformAxes, } from "./fishnet/types.ts";
26
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AACjH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,oBAAoB,EACpB,8BAA8B,EAC9B,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,YAAY,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,EAChC,uBAAuB,EACvB,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,EACzB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AACjH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,oBAAoB,EACpB,8BAA8B,EAC9B,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,EAChC,uBAAuB,EACvB,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,EACzB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -9,6 +9,13 @@ export interface CaptureConfig {
9
9
  targetProcessName?: string;
10
10
  /** Stable Npcap device name. Omit to select the default-route adapter automatically. */
11
11
  deviceName?: string;
12
+ /**
13
+ * Collapse the redundant copies a redirecting VPN puts on the wire. Defaults to true.
14
+ *
15
+ * Without this a relayed datagram is captured more than once and counted twice downstream.
16
+ * Disable only to inspect the wire exactly as observed.
17
+ */
18
+ suppressDuplicates?: boolean;
12
19
  /** Decode captured UDP payloads as LiteNetLib 1.x packets. Defaults to false. */
13
20
  decodeLiteNetLib?: boolean;
14
21
  /** Decode verified FishNet headers inside LiteNetLib data packets. Implies decodeLiteNetLib. */
@@ -49,5 +56,10 @@ export interface CaptureTargetStatus {
49
56
  processIds: number[];
50
57
  }
51
58
  export type CaptureState = "stopped" | "starting" | "running" | "stopping";
59
+ /** A transport connection opening or closing, reported as LiteNetLib announces it. */
60
+ export interface CaptureConnectionEvent {
61
+ connectionId: string;
62
+ state: "opened" | "closed";
63
+ }
52
64
  export {};
53
65
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC,sGAAsG;IACtG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2FAA2F;IAC3F,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,UAAU,kBAAkB;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,IAAI,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC,sGAAsG;IACtG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qFAAqF;IACrF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2FAA2F;IAC3F,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,UAAU,kBAAkB;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,IAAI,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAE3E,sFAAsF;AACtF,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kar-mi/spirit-vale-tools-capture",
3
- "version": "2.5.1",
3
+ "version": "2.7.0",
4
4
  "description": "Passive Windows packet capture and Spirit Vale protocol decoding for Bun.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {