@kar-mi/spirit-vale-tools-capture 2.4.2 → 2.5.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.
@@ -5,5 +5,5 @@ export interface AdapterResolution {
5
5
  detail?: string;
6
6
  }
7
7
  export declare function resolveCaptureDevice(devices: readonly NpcapDevice[], requestedName?: string): Promise<AdapterResolution>;
8
- export declare function chooseDeviceByRouteOutput(devices: readonly NpcapDevice[], output: string): NpcapDevice | undefined;
8
+ export declare function chooseDeviceByRouteAddress(devices: readonly NpcapDevice[], address: string): NpcapDevice | undefined;
9
9
  //# sourceMappingURL=adapter-selection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-selection.d.ts","sourceRoot":"","sources":["../../src/capture/adapter-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAa5B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAYlH"}
1
+ {"version":3,"file":"adapter-selection.d.ts","sourceRoot":"","sources":["../../src/capture/adapter-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAa5B;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,SAAS,CAEzB"}
@@ -1,4 +1,4 @@
1
- export type NpcapAvailability = "ready" | "missing" | "admin-only" | "error";
1
+ export type NpcapAvailability = "ready" | "missing" | "error";
2
2
  export interface NpcapStatus {
3
3
  availability: NpcapAvailability;
4
4
  detail: string;
@@ -32,6 +32,7 @@ export declare class SystemNpcapRuntime implements NpcapRuntime {
32
32
  private readyStatus?;
33
33
  status(): Promise<NpcapStatus>;
34
34
  listDevices(): Promise<NpcapDevice[]>;
35
+ private enumerateDevices;
35
36
  open(device: NpcapDevice, filter: string): Promise<NpcapSession>;
36
37
  private load;
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"npcap.d.ts","sourceRoot":"","sources":["../../src/capture/npcap.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,iBAAiB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,IAAI,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,UAAU,IAAI,WAAW,GAAG,SAAS,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAClE;AAID,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,GAAG,CAAC,CAAc;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAc;IAE5B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CA0BnC;IAEK,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAiC1C;IAEK,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAiCrE;IAED,OAAO,CAAC,IAAI;CAyBb"}
1
+ {"version":3,"file":"npcap.d.ts","sourceRoot":"","sources":["../../src/capture/npcap.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,iBAAiB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,IAAI,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,UAAU,IAAI,WAAW,GAAG,SAAS,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAClE;AAKD,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,GAAG,CAAC,CAAc;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAc;IAE5B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CA4BnC;IAEK,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAI1C;IAED,OAAO,CAAC,gBAAgB;IAgClB,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAsCrE;IAED,OAAO,CAAC,IAAI;CAyBb"}
@@ -1,10 +1,6 @@
1
1
  import type { CaptureProtocol, CapturedTransportPacket, CaptureTargetStatus } from "../types.ts";
2
- interface OwnedEndpoint {
3
- protocol: CaptureProtocol;
4
- address: string;
5
- port: number;
6
- processId: number;
7
- }
2
+ import type { WindowsOwnedEndpoint } from "./win32-system.ts";
3
+ type OwnedEndpoint = WindowsOwnedEndpoint;
8
4
  export interface TargetSnapshotProvider {
9
5
  snapshot(processName: string, protocols: readonly CaptureProtocol[]): Promise<{
10
6
  processIds: number[];
@@ -31,7 +27,5 @@ export declare class WindowsTargetTracker {
31
27
  private matches;
32
28
  private refresh;
33
29
  }
34
- export declare function parseTaskList(output: string): number[];
35
- export declare function parseNetstat(output: string, protocol: CaptureProtocol): OwnedEndpoint[];
36
30
  export {};
37
31
  //# sourceMappingURL=target-tracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"target-tracker.d.ts","sourceRoot":"","sources":["../../src/capture/target-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEjG,UAAU,aAAa;IACrB,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC;QAC5E,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,EAAE,aAAa,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ;AAED,qBAAa,oBAAoB;IAS7B,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAbpC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,YACW,WAAW,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,eAAe,EAAE,EACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,EAC/C,QAAQ,GAAE,sBAA4D,EACtE,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,EAC/C,iBAAiB,SAA6B,EAC7D;IAEE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,IAAI,IAAI,IAAI,CAGX;IAED,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAM5E;IAED,OAAO,CAAC,OAAO;YAMD,OAAO;CAgCtB;AA+BD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAKtD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,aAAa,EAAE,CAQvF"}
1
+ {"version":3,"file":"target-tracker.d.ts","sourceRoot":"","sources":["../../src/capture/target-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEjG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,KAAK,aAAa,GAAG,oBAAoB,CAAC;AAI1C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC;QAC5E,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,EAAE,aAAa,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ;AAED,qBAAa,oBAAoB;IAS7B,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAbpC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,YACW,WAAW,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,eAAe,EAAE,EACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,EAC/C,QAAQ,GAAE,sBAA4D,EACtE,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,EAC/C,iBAAiB,SAA6B,EAC7D;IAEE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,IAAI,IAAI,IAAI,CAGX;IAED,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAM5E;IAED,OAAO,CAAC,OAAO;YAMD,OAAO;CAgCtB"}
@@ -0,0 +1,11 @@
1
+ import type { CaptureProtocol } from "../types.ts";
2
+ export interface WindowsOwnedEndpoint {
3
+ protocol: CaptureProtocol;
4
+ address: string;
5
+ port: number;
6
+ processId: number;
7
+ }
8
+ export declare function findProcessIdsByName(processName: string): number[];
9
+ export declare function listOwnedEndpoints(protocols: readonly CaptureProtocol[]): WindowsOwnedEndpoint[];
10
+ export declare function defaultRouteIpv4Address(): string | undefined;
11
+ //# sourceMappingURL=win32-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"win32-system.d.ts","sourceRoot":"","sources":["../../src/capture/win32-system.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAmDD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBlE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,oBAAoB,EAAE,CAIhG;AAED,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAe5D"}
package/dist/capture.js CHANGED
@@ -15235,53 +15235,8 @@ function decodeFishNetBundle(payload, options = { reliable: false }) {
15235
15235
  return new FishNetSessionDecoder(options.rpcMap).decode(payload, options);
15236
15236
  }
15237
15237
 
15238
- // src/capture/adapter-selection.ts
15239
- async function resolveCaptureDevice(devices, requestedName) {
15240
- if (requestedName) {
15241
- const requested = devices.find((device) => device.name === requestedName);
15242
- if (requested)
15243
- return { device: requested, usedFallback: false };
15244
- }
15245
- const automatic = await defaultRouteDevice(devices) ?? devices.find(isUsable) ?? devices[0];
15246
- return {
15247
- device: automatic,
15248
- usedFallback: Boolean(requestedName),
15249
- ...requestedName && automatic ? { detail: "The saved adapter is unavailable; capture is using the automatically selected adapter" } : {}
15250
- };
15251
- }
15252
- function chooseDeviceByRouteOutput(devices, output) {
15253
- const routes = output.split(/\r?\n/).flatMap((line) => {
15254
- const columns = line.trim().split(/\s+/);
15255
- if (columns[0] !== "0.0.0.0" || columns[1] !== "0.0.0.0" || columns.length < 5)
15256
- return [];
15257
- const metric = Number(columns[4]);
15258
- return [{ address: columns[3], metric: Number.isFinite(metric) ? metric : Number.MAX_SAFE_INTEGER }];
15259
- }).sort((left, right) => left.metric - right.metric);
15260
- for (const route of routes) {
15261
- const match = devices.find((device) => device.addresses.includes(route.address));
15262
- if (match)
15263
- return match;
15264
- }
15265
- return;
15266
- }
15267
- async function defaultRouteDevice(devices) {
15268
- try {
15269
- const child = Bun.spawn({
15270
- cmd: ["route.exe", "PRINT", "0.0.0.0"],
15271
- stdout: "pipe",
15272
- stderr: "ignore",
15273
- windowsHide: true
15274
- });
15275
- const output = await new Response(child.stdout).text();
15276
- return await child.exited === 0 ? chooseDeviceByRouteOutput(devices, output) : undefined;
15277
- } catch {
15278
- return;
15279
- }
15280
- }
15281
- function isUsable(device) {
15282
- const label = `${device.name} ${device.description}`.toLowerCase();
15283
- return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
15284
- }
15238
+ // src/capture/win32-system.ts
15239
+ import { dlopen, FFIType } from "bun:ffi";
15285
15240
 
15286
15241
  // src/capture/ip-address.ts
15287
15242
  function formatIpv6(data) {
@@ -15309,6 +15264,194 @@ function formatIpv6(data) {
15309
15264
  return `${before}::${after}`;
15310
15265
  }
15311
15266
 
15267
+ // src/capture/win32-system.ts
15268
+ var ERROR_SUCCESS = 0;
15269
+ var ERROR_INSUFFICIENT_BUFFER = 122;
15270
+ var TH32CS_SNAPPROCESS = 2;
15271
+ var AF_INET = 2;
15272
+ var AF_INET6 = 23;
15273
+ var TCP_TABLE_OWNER_PID_ALL = 5;
15274
+ var UDP_TABLE_OWNER_PID = 1;
15275
+ var ENDPOINT_LAYOUTS = [
15276
+ { protocol: "tcp", family: AF_INET, rowSize: 24, addressOffset: 4, portOffset: 8, processIdOffset: 20 },
15277
+ { protocol: "tcp", family: AF_INET6, rowSize: 56, addressOffset: 0, portOffset: 20, processIdOffset: 52 },
15278
+ { protocol: "udp", family: AF_INET, rowSize: 12, addressOffset: 0, portOffset: 4, processIdOffset: 8 },
15279
+ { protocol: "udp", family: AF_INET6, rowSize: 28, addressOffset: 0, portOffset: 20, processIdOffset: 24 }
15280
+ ];
15281
+ var ROUTE_SIZE = 56;
15282
+ var ROUTE_INTERFACE_INDEX_OFFSET = 16;
15283
+ var ADDRESS_ROW_SIZE = 24;
15284
+ var ADDRESS_ROW_INTERFACE_INDEX_OFFSET = 4;
15285
+ var PROCESS_ENTRY_SIZE = process.arch === "ia32" ? 556 : 568;
15286
+ var PROCESS_ENTRY_ID_OFFSET = 8;
15287
+ var PROCESS_ENTRY_NAME_OFFSET = process.arch === "ia32" ? 36 : 44;
15288
+ var kernel32;
15289
+ var ipHelper;
15290
+ function findProcessIdsByName(processName) {
15291
+ requireWindows();
15292
+ const api = loadKernel32();
15293
+ const snapshot = api.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
15294
+ if (snapshot === null || snapshot === -1)
15295
+ throw new Error("Windows could not create a process snapshot");
15296
+ const entry = new Uint8Array(PROCESS_ENTRY_SIZE);
15297
+ const view = new DataView(entry.buffer);
15298
+ view.setUint32(0, PROCESS_ENTRY_SIZE, true);
15299
+ const expected = processName.toLowerCase();
15300
+ const processIds = [];
15301
+ try {
15302
+ let hasEntry = api.Process32FirstW(snapshot, entry) !== 0;
15303
+ while (hasEntry) {
15304
+ if (decodeWideString(entry, PROCESS_ENTRY_NAME_OFFSET).toLowerCase() === expected) {
15305
+ processIds.push(view.getUint32(PROCESS_ENTRY_ID_OFFSET, true));
15306
+ }
15307
+ hasEntry = api.Process32NextW(snapshot, entry) !== 0;
15308
+ }
15309
+ } finally {
15310
+ api.CloseHandle(snapshot);
15311
+ }
15312
+ return processIds.sort((left, right) => left - right);
15313
+ }
15314
+ function listOwnedEndpoints(protocols) {
15315
+ requireWindows();
15316
+ const selected = new Set(protocols);
15317
+ return ENDPOINT_LAYOUTS.filter((layout) => selected.has(layout.protocol)).flatMap(readEndpoints);
15318
+ }
15319
+ function defaultRouteIpv4Address() {
15320
+ requireWindows();
15321
+ const api = loadIpHelper();
15322
+ const route = new Uint8Array(ROUTE_SIZE);
15323
+ if (api.GetBestRoute(0, 0, route) !== ERROR_SUCCESS)
15324
+ return;
15325
+ const interfaceIndex = new DataView(route.buffer).getUint32(ROUTE_INTERFACE_INDEX_OFFSET, true);
15326
+ const table = readVariableTable((buffer, size) => api.GetIpAddrTable(buffer, size, 0));
15327
+ const view = tableView(table, ADDRESS_ROW_SIZE);
15328
+ for (let index = 0;index < view.count; index += 1) {
15329
+ const offset = 4 + index * ADDRESS_ROW_SIZE;
15330
+ if (view.data.getUint32(offset + ADDRESS_ROW_INTERFACE_INDEX_OFFSET, true) === interfaceIndex) {
15331
+ return formatIpv4(table, offset);
15332
+ }
15333
+ }
15334
+ return;
15335
+ }
15336
+ function readEndpoints(layout) {
15337
+ const api = loadIpHelper();
15338
+ const table = readVariableTable((buffer, size) => layout.protocol === "tcp" ? api.GetExtendedTcpTable(buffer, size, 0, layout.family, TCP_TABLE_OWNER_PID_ALL, 0) : api.GetExtendedUdpTable(buffer, size, 0, layout.family, UDP_TABLE_OWNER_PID, 0));
15339
+ const view = tableView(table, layout.rowSize);
15340
+ const endpoints = [];
15341
+ for (let index = 0;index < view.count; index += 1) {
15342
+ const offset = 4 + index * layout.rowSize;
15343
+ endpoints.push({
15344
+ protocol: layout.protocol,
15345
+ address: readAddress(table, offset + layout.addressOffset, layout.family),
15346
+ port: view.data.getUint16(offset + layout.portOffset, false),
15347
+ processId: view.data.getUint32(offset + layout.processIdOffset, true)
15348
+ });
15349
+ }
15350
+ return endpoints;
15351
+ }
15352
+ function readVariableTable(call) {
15353
+ const size = new Uint32Array(1);
15354
+ const initialResult = call(null, size);
15355
+ if (initialResult !== ERROR_INSUFFICIENT_BUFFER || !size[0]) {
15356
+ throw new Error(`Windows table size query failed with error ${initialResult}`);
15357
+ }
15358
+ for (let attempt = 0;attempt < 3; attempt += 1) {
15359
+ const capacity = size[0] ?? 0;
15360
+ const buffer = new Uint8Array(capacity);
15361
+ size[0] = buffer.byteLength;
15362
+ const result = call(buffer, size);
15363
+ if (result === ERROR_SUCCESS)
15364
+ return buffer.subarray(0, size[0]);
15365
+ if (result !== ERROR_INSUFFICIENT_BUFFER || size[0] === 0) {
15366
+ throw new Error(`Windows table query failed with error ${result}`);
15367
+ }
15368
+ }
15369
+ throw new Error("Windows table changed too frequently to read consistently");
15370
+ }
15371
+ function tableView(table, rowSize) {
15372
+ if (table.byteLength < 4)
15373
+ throw new Error("Windows returned a truncated table");
15374
+ const data = new DataView(table.buffer, table.byteOffset, table.byteLength);
15375
+ const count = data.getUint32(0, true);
15376
+ if (4 + count * rowSize > table.byteLength)
15377
+ throw new Error("Windows returned an invalid table size");
15378
+ return { count, data };
15379
+ }
15380
+ function readAddress(buffer, offset, family) {
15381
+ return family === AF_INET ? formatIpv4(buffer, offset) : formatIpv6(Buffer.from(buffer.subarray(offset, offset + 16)));
15382
+ }
15383
+ function formatIpv4(buffer, offset) {
15384
+ return `${buffer[offset]}.${buffer[offset + 1]}.${buffer[offset + 2]}.${buffer[offset + 3]}`;
15385
+ }
15386
+ function decodeWideString(buffer, offset) {
15387
+ let end = offset;
15388
+ while (end + 1 < buffer.byteLength && (buffer[end] !== 0 || buffer[end + 1] !== 0))
15389
+ end += 2;
15390
+ return new TextDecoder("utf-16le").decode(buffer.subarray(offset, end));
15391
+ }
15392
+ function requireWindows() {
15393
+ if (process.platform !== "win32")
15394
+ throw new Error("Windows system APIs are unavailable on this platform");
15395
+ }
15396
+ function loadKernel32() {
15397
+ if (kernel32)
15398
+ return kernel32;
15399
+ kernel32 = dlopen("kernel32.dll", {
15400
+ CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.ptr },
15401
+ Process32FirstW: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15402
+ Process32NextW: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15403
+ CloseHandle: { args: [FFIType.ptr], returns: FFIType.i32 }
15404
+ }).symbols;
15405
+ return kernel32;
15406
+ }
15407
+ function loadIpHelper() {
15408
+ if (ipHelper)
15409
+ return ipHelper;
15410
+ ipHelper = dlopen("iphlpapi.dll", {
15411
+ GetBestRoute: { args: [FFIType.u32, FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
15412
+ GetIpAddrTable: { args: [FFIType.ptr, FFIType.ptr, FFIType.i32], returns: FFIType.u32 },
15413
+ GetExtendedTcpTable: {
15414
+ args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.u32, FFIType.u32, FFIType.u32],
15415
+ returns: FFIType.u32
15416
+ },
15417
+ GetExtendedUdpTable: {
15418
+ args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.u32, FFIType.u32, FFIType.u32],
15419
+ returns: FFIType.u32
15420
+ }
15421
+ }).symbols;
15422
+ return ipHelper;
15423
+ }
15424
+
15425
+ // src/capture/adapter-selection.ts
15426
+ async function resolveCaptureDevice(devices, requestedName) {
15427
+ if (requestedName) {
15428
+ const requested = devices.find((device) => device.name === requestedName);
15429
+ if (requested)
15430
+ return { device: requested, usedFallback: false };
15431
+ }
15432
+ const automatic = defaultRouteDevice(devices) ?? devices.find(isUsable) ?? devices[0];
15433
+ return {
15434
+ device: automatic,
15435
+ usedFallback: Boolean(requestedName),
15436
+ ...requestedName && automatic ? { detail: "The saved adapter is unavailable; capture is using the automatically selected adapter" } : {}
15437
+ };
15438
+ }
15439
+ function chooseDeviceByRouteAddress(devices, address) {
15440
+ return devices.find((device) => device.addresses.includes(address));
15441
+ }
15442
+ function defaultRouteDevice(devices) {
15443
+ try {
15444
+ const address = defaultRouteIpv4Address();
15445
+ return address ? chooseDeviceByRouteAddress(devices, address) : undefined;
15446
+ } catch {
15447
+ return;
15448
+ }
15449
+ }
15450
+ function isUsable(device) {
15451
+ const label = `${device.name} ${device.description}`.toLowerCase();
15452
+ return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
15453
+ }
15454
+
15312
15455
  // src/capture/link-layer.ts
15313
15456
  var DATA_LINK = {
15314
15457
  NULL: 0,
@@ -15352,8 +15495,9 @@ function ipVersion(data) {
15352
15495
  // src/capture/npcap.ts
15353
15496
  import { existsSync } from "fs";
15354
15497
  import path from "path";
15355
- import { CString, dlopen, FFIType, read, toArrayBuffer } from "bun:ffi";
15498
+ import { CString, dlopen as dlopen2, FFIType as FFIType2, read, toArrayBuffer } from "bun:ffi";
15356
15499
  var ERROR_BUFFER_SIZE = 512;
15500
+ var PCAP_ERROR_PERM_DENIED = -8;
15357
15501
 
15358
15502
  class SystemNpcapRuntime {
15359
15503
  api;
@@ -15368,18 +15512,20 @@ class SystemNpcapRuntime {
15368
15512
  if (!existsSync(dllPath)) {
15369
15513
  return { availability: "missing", detail: "Npcap is not installed" };
15370
15514
  }
15371
- if (await isAdminOnlyInstall()) {
15372
- return {
15373
- availability: "admin-only",
15374
- detail: "Npcap is installed for administrators only; reinstall it with that restriction unchecked"
15375
- };
15376
- }
15377
15515
  try {
15378
15516
  const api = this.load();
15379
15517
  const version = String(api.symbols.pcap_lib_version());
15380
15518
  if (!version.toLowerCase().includes("npcap")) {
15381
15519
  return { availability: "error", detail: "The loaded packet capture library is not Npcap" };
15382
15520
  }
15521
+ const devices = this.enumerateDevices(api);
15522
+ if (devices.length === 0) {
15523
+ return {
15524
+ availability: "error",
15525
+ detail: "Npcap reported no captureable network adapters; access may require elevation",
15526
+ version
15527
+ };
15528
+ }
15383
15529
  this.readyStatus = { availability: "ready", detail: "Npcap is ready", version };
15384
15530
  return this.readyStatus;
15385
15531
  } catch (error) {
@@ -15390,7 +15536,9 @@ class SystemNpcapRuntime {
15390
15536
  const status = await this.status();
15391
15537
  if (status.availability !== "ready")
15392
15538
  throw new Error(status.detail);
15393
- const api = this.load();
15539
+ return this.enumerateDevices(this.load());
15540
+ }
15541
+ enumerateDevices(api) {
15394
15542
  const resultPointer = new Uint8Array(8);
15395
15543
  const errorBuffer = new Uint8Array(ERROR_BUFFER_SIZE);
15396
15544
  const result = api.symbols.pcap_findalldevs(resultPointer, errorBuffer);
@@ -15440,6 +15588,9 @@ class SystemNpcapRuntime {
15440
15588
  check(api.symbols.pcap_set_buffer_size(handle, 10 * 1024 * 1024), handle, api, "set capture buffer size");
15441
15589
  check(api.symbols.pcap_set_immediate_mode(handle, 1), handle, api, "enable immediate mode");
15442
15590
  const activated = api.symbols.pcap_activate(handle);
15591
+ if (activated === PCAP_ERROR_PERM_DENIED) {
15592
+ throw new Error(`Npcap denied access to ${device.description}; run elevated or reinstall Npcap without administrator-only access: ${pcapError(api, handle)}`);
15593
+ }
15443
15594
  if (activated < 0)
15444
15595
  throw new Error(`Npcap could not activate ${device.description}: ${pcapError(api, handle)}`);
15445
15596
  const dataLink = api.symbols.pcap_datalink(handle);
@@ -15463,25 +15614,25 @@ class SystemNpcapRuntime {
15463
15614
  load() {
15464
15615
  if (this.api)
15465
15616
  return this.api;
15466
- const library = dlopen(npcapDllPath(), {
15467
- pcap_lib_version: { args: [], returns: FFIType.cstring },
15468
- pcap_findalldevs: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15469
- pcap_freealldevs: { args: [FFIType.ptr], returns: FFIType.void },
15470
- pcap_create: { args: [FFIType.cstring, FFIType.ptr], returns: FFIType.ptr },
15471
- pcap_set_snaplen: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15472
- pcap_set_promisc: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15473
- pcap_set_timeout: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15474
- pcap_set_buffer_size: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15475
- pcap_set_immediate_mode: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15476
- pcap_activate: { args: [FFIType.ptr], returns: FFIType.i32 },
15477
- pcap_datalink: { args: [FFIType.ptr], returns: FFIType.i32 },
15478
- pcap_compile: { args: [FFIType.ptr, FFIType.ptr, FFIType.cstring, FFIType.i32, FFIType.u32], returns: FFIType.i32 },
15479
- pcap_setfilter: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15480
- pcap_freecode: { args: [FFIType.ptr], returns: FFIType.void },
15481
- pcap_setnonblock: { args: [FFIType.ptr, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
15482
- pcap_next_ex: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15483
- pcap_geterr: { args: [FFIType.ptr], returns: FFIType.cstring },
15484
- pcap_close: { args: [FFIType.ptr], returns: FFIType.void }
15617
+ const library = dlopen2(npcapDllPath(), {
15618
+ pcap_lib_version: { args: [], returns: FFIType2.cstring },
15619
+ pcap_findalldevs: { args: [FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
15620
+ pcap_freealldevs: { args: [FFIType2.ptr], returns: FFIType2.void },
15621
+ pcap_create: { args: [FFIType2.cstring, FFIType2.ptr], returns: FFIType2.ptr },
15622
+ pcap_set_snaplen: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
15623
+ pcap_set_promisc: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
15624
+ pcap_set_timeout: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
15625
+ pcap_set_buffer_size: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
15626
+ pcap_set_immediate_mode: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
15627
+ pcap_activate: { args: [FFIType2.ptr], returns: FFIType2.i32 },
15628
+ pcap_datalink: { args: [FFIType2.ptr], returns: FFIType2.i32 },
15629
+ pcap_compile: { args: [FFIType2.ptr, FFIType2.ptr, FFIType2.cstring, FFIType2.i32, FFIType2.u32], returns: FFIType2.i32 },
15630
+ pcap_setfilter: { args: [FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
15631
+ pcap_freecode: { args: [FFIType2.ptr], returns: FFIType2.void },
15632
+ pcap_setnonblock: { args: [FFIType2.ptr, FFIType2.i32, FFIType2.ptr], returns: FFIType2.i32 },
15633
+ pcap_next_ex: { args: [FFIType2.ptr, FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
15634
+ pcap_geterr: { args: [FFIType2.ptr], returns: FFIType2.cstring },
15635
+ pcap_close: { args: [FFIType2.ptr], returns: FFIType2.void }
15485
15636
  });
15486
15637
  this.api = library;
15487
15638
  return this.api;
@@ -15575,20 +15726,6 @@ function readCStringBuffer(buffer) {
15575
15726
  function npcapDllPath() {
15576
15727
  return path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "Npcap", "wpcap.dll");
15577
15728
  }
15578
- async function isAdminOnlyInstall() {
15579
- try {
15580
- const child = Bun.spawn({
15581
- cmd: ["reg.exe", "query", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\npcap\\Parameters", "/v", "AdminOnly"],
15582
- stdout: "pipe",
15583
- stderr: "ignore",
15584
- windowsHide: true
15585
- });
15586
- const output = await new Response(child.stdout).text();
15587
- return await child.exited === 0 && /AdminOnly\s+REG_DWORD\s+0x1\b/i.test(output);
15588
- } catch {
15589
- return false;
15590
- }
15591
- }
15592
15729
  function errorMessage(error) {
15593
15730
  return error instanceof Error ? error.message : String(error);
15594
15731
  }
@@ -15614,7 +15751,7 @@ function parseIpv4(data, context) {
15614
15751
  const declaredLength = data.readUInt16BE(2);
15615
15752
  if (declaredLength < headerLength)
15616
15753
  return;
15617
- return parseTransport(data, headerLength, Math.min(declaredLength, data.length), declaredLength > data.length, data[9], 4, formatIpv4(data, 12), formatIpv4(data, 16), context);
15754
+ return parseTransport(data, headerLength, Math.min(declaredLength, data.length), declaredLength > data.length, data[9], 4, formatIpv42(data, 12), formatIpv42(data, 16), context);
15618
15755
  }
15619
15756
  function parseIpv6(data, context) {
15620
15757
  if (data.length < 40)
@@ -15685,7 +15822,7 @@ function parseTransport(data, offset, packetEnd, truncated, protocol, ipVersion2
15685
15822
  }
15686
15823
  return;
15687
15824
  }
15688
- function formatIpv4(data, offset) {
15825
+ function formatIpv42(data, offset) {
15689
15826
  return `${data[offset]}.${data[offset + 1]}.${data[offset + 2]}.${data[offset + 3]}`;
15690
15827
  }
15691
15828
 
@@ -15705,7 +15842,7 @@ class WindowsTargetTracker {
15705
15842
  refreshing = false;
15706
15843
  published = false;
15707
15844
  lastRefreshError;
15708
- constructor(processName, protocols, onStatus, provider = new CommandTargetSnapshotProvider, onWarning = () => {}, refreshIntervalMs = TARGET_REFRESH_INTERVAL_MS) {
15845
+ constructor(processName, protocols, onStatus, provider = new WindowsTargetSnapshotProvider, onWarning = () => {}, refreshIntervalMs = TARGET_REFRESH_INTERVAL_MS) {
15709
15846
  this.processName = processName;
15710
15847
  this.protocols = protocols;
15711
15848
  this.onStatus = onStatus;
@@ -15766,58 +15903,18 @@ class WindowsTargetTracker {
15766
15903
  }
15767
15904
  }
15768
15905
 
15769
- class CommandTargetSnapshotProvider {
15906
+ class WindowsTargetSnapshotProvider {
15770
15907
  async snapshot(processName, protocols) {
15771
- const process2 = Bun.spawn({
15772
- cmd: ["tasklist.exe", "/FI", `IMAGENAME eq ${processName}`, "/FO", "CSV", "/NH"],
15773
- stdout: "pipe",
15774
- stderr: "ignore",
15775
- windowsHide: true
15776
- });
15777
- const processOutput = await new Response(process2.stdout).text();
15778
- const processIds = await process2.exited === 0 ? parseTaskList(processOutput) : [];
15908
+ const processIds = findProcessIdsByName(processName);
15779
15909
  if (processIds.length === 0)
15780
15910
  return { processIds, endpoints: [] };
15781
- const outputs = await Promise.all(protocols.map(async (protocol) => {
15782
- const child = Bun.spawn({
15783
- cmd: ["netstat.exe", "-ano", "-p", protocol.toUpperCase()],
15784
- stdout: "pipe",
15785
- stderr: "ignore",
15786
- windowsHide: true
15787
- });
15788
- const output = await new Response(child.stdout).text();
15789
- return await child.exited === 0 ? parseNetstat(output, protocol) : [];
15790
- }));
15791
15911
  const selected = new Set(processIds);
15792
- return { processIds, endpoints: outputs.flat().filter((endpoint) => selected.has(endpoint.processId)) };
15912
+ return {
15913
+ processIds,
15914
+ endpoints: listOwnedEndpoints(protocols).filter((endpoint) => selected.has(endpoint.processId))
15915
+ };
15793
15916
  }
15794
15917
  }
15795
- function parseTaskList(output) {
15796
- return output.split(/\r?\n/).flatMap((line) => {
15797
- const match = line.match(/^"(?:[^"]|"")*","(\d+)"/);
15798
- return match ? [Number(match[1])] : [];
15799
- }).sort((left, right) => left - right);
15800
- }
15801
- function parseNetstat(output, protocol) {
15802
- return output.split(/\r?\n/).flatMap((line) => {
15803
- const columns = line.trim().split(/\s+/);
15804
- if (columns[0]?.toLowerCase() !== protocol || columns.length < (protocol === "tcp" ? 5 : 4))
15805
- return [];
15806
- const local = parseEndpoint(columns[1]);
15807
- const processId = Number(columns[protocol === "tcp" ? 4 : 3]);
15808
- return local && Number.isInteger(processId) ? [{ protocol, ...local, processId }] : [];
15809
- });
15810
- }
15811
- function parseEndpoint(value) {
15812
- const bracketed = value.match(/^\[(.*)]:(\d+)$/);
15813
- if (bracketed)
15814
- return { address: bracketed[1], port: Number(bracketed[2]) };
15815
- const split = value.lastIndexOf(":");
15816
- if (split < 0)
15817
- return;
15818
- const port = Number(value.slice(split + 1));
15819
- return Number.isInteger(port) ? { address: value.slice(0, split), port } : undefined;
15820
- }
15821
15918
  function endpointKey(protocol, address, port) {
15822
15919
  return `${protocol}|${address}|${port}`;
15823
15920
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kar-mi/spirit-vale-tools-capture",
3
- "version": "2.4.2",
3
+ "version": "2.5.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": {