@kar-mi/spirit-vale-tools-capture 2.4.1 → 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.
Files changed (90) hide show
  1. package/README.md +20 -20
  2. package/dist/capture/adapter-selection.d.ts +1 -1
  3. package/dist/capture/adapter-selection.d.ts.map +1 -1
  4. package/dist/capture/npcap.d.ts +2 -1
  5. package/dist/capture/npcap.d.ts.map +1 -1
  6. package/dist/capture/target-tracker.d.ts +2 -8
  7. package/dist/capture/target-tracker.d.ts.map +1 -1
  8. package/dist/capture/win32-system.d.ts +11 -0
  9. package/dist/capture/win32-system.d.ts.map +1 -0
  10. package/dist/capture.js +328 -149
  11. package/dist/fishnet/builtin-maps.d.ts +63 -0
  12. package/dist/fishnet/builtin-maps.d.ts.map +1 -1
  13. package/dist/fishnet/definitions/codecs.d.ts +1 -1
  14. package/dist/fishnet/definitions/codecs.d.ts.map +1 -1
  15. package/dist/fishnet/rpc-definitions/generated/index.d.ts +63 -0
  16. package/dist/fishnet/rpc-definitions/generated/index.d.ts.map +1 -1
  17. package/dist/fishnet/rpc-definitions/generated/player-controller.d.ts +4 -0
  18. package/dist/fishnet/rpc-definitions/generated/player-controller.d.ts.map +1 -1
  19. package/dist/fishnet/rpc-definitions/generated/player-save.d.ts +27 -0
  20. package/dist/fishnet/rpc-definitions/generated/player-save.d.ts.map +1 -1
  21. package/dist/fishnet/rpc-definitions/generated/skills-component.d.ts +1 -0
  22. package/dist/fishnet/rpc-definitions/generated/skills-component.d.ts.map +1 -1
  23. package/dist/fishnet/rpc-definitions/generated/summoning-component.d.ts +31 -0
  24. package/dist/fishnet/rpc-definitions/generated/summoning-component.d.ts.map +1 -1
  25. package/dist/fishnet/rpc-definitions/index.d.ts +63 -0
  26. package/dist/fishnet/rpc-definitions/index.d.ts.map +1 -1
  27. package/dist/index.js +94 -12
  28. package/package.json +9 -9
  29. package/dist/fishnet/rpc-definitions/broadcasts.d.ts +0 -23
  30. package/dist/fishnet/rpc-definitions/broadcasts.d.ts.map +0 -1
  31. package/dist/fishnet/rpc-definitions/current-build.d.ts +0 -8501
  32. package/dist/fishnet/rpc-definitions/current-build.d.ts.map +0 -1
  33. package/dist/fishnet/rpc-definitions/fishnet/network-animator.d.ts +0 -42
  34. package/dist/fishnet/rpc-definitions/fishnet/network-animator.d.ts.map +0 -1
  35. package/dist/fishnet/rpc-definitions/fishnet/network-transform.d.ts +0 -138
  36. package/dist/fishnet/rpc-definitions/fishnet/network-transform.d.ts.map +0 -1
  37. package/dist/fishnet/rpc-definitions/fishnet/predicted-owner.d.ts +0 -26
  38. package/dist/fishnet/rpc-definitions/fishnet/predicted-owner.d.ts.map +0 -1
  39. package/dist/fishnet/rpc-definitions/game/base-unit-controller.d.ts +0 -122
  40. package/dist/fishnet/rpc-definitions/game/base-unit-controller.d.ts.map +0 -1
  41. package/dist/fishnet/rpc-definitions/game/combat-component.d.ts +0 -44
  42. package/dist/fishnet/rpc-definitions/game/combat-component.d.ts.map +0 -1
  43. package/dist/fishnet/rpc-definitions/game/health-component.d.ts +0 -306
  44. package/dist/fishnet/rpc-definitions/game/health-component.d.ts.map +0 -1
  45. package/dist/fishnet/rpc-definitions/game/loot-drop.d.ts +0 -104
  46. package/dist/fishnet/rpc-definitions/game/loot-drop.d.ts.map +0 -1
  47. package/dist/fishnet/rpc-definitions/game/monster-controller.d.ts +0 -144
  48. package/dist/fishnet/rpc-definitions/game/monster-controller.d.ts.map +0 -1
  49. package/dist/fishnet/rpc-definitions/game/move-component.d.ts +0 -74
  50. package/dist/fishnet/rpc-definitions/game/move-component.d.ts.map +0 -1
  51. package/dist/fishnet/rpc-definitions/game/player-controller/chat-presence.d.ts +0 -332
  52. package/dist/fishnet/rpc-definitions/game/player-controller/chat-presence.d.ts.map +0 -1
  53. package/dist/fishnet/rpc-definitions/game/player-controller/endgame-pvp.d.ts +0 -89
  54. package/dist/fishnet/rpc-definitions/game/player-controller/endgame-pvp.d.ts.map +0 -1
  55. package/dist/fishnet/rpc-definitions/game/player-controller/guild.d.ts +0 -393
  56. package/dist/fishnet/rpc-definitions/game/player-controller/guild.d.ts.map +0 -1
  57. package/dist/fishnet/rpc-definitions/game/player-controller/index.d.ts +0 -23
  58. package/dist/fishnet/rpc-definitions/game/player-controller/index.d.ts.map +0 -1
  59. package/dist/fishnet/rpc-definitions/game/player-controller/party.d.ts +0 -168
  60. package/dist/fishnet/rpc-definitions/game/player-controller/party.d.ts.map +0 -1
  61. package/dist/fishnet/rpc-definitions/game/player-controller/trade-market.d.ts +0 -232
  62. package/dist/fishnet/rpc-definitions/game/player-controller/trade-market.d.ts.map +0 -1
  63. package/dist/fishnet/rpc-definitions/game/player-controller/traversal-session.d.ts +0 -58
  64. package/dist/fishnet/rpc-definitions/game/player-controller/traversal-session.d.ts.map +0 -1
  65. package/dist/fishnet/rpc-definitions/game/player-save/character-build.d.ts +0 -266
  66. package/dist/fishnet/rpc-definitions/game/player-save/character-build.d.ts.map +0 -1
  67. package/dist/fishnet/rpc-definitions/game/player-save/character-data.d.ts +0 -991
  68. package/dist/fishnet/rpc-definitions/game/player-save/character-data.d.ts.map +0 -1
  69. package/dist/fishnet/rpc-definitions/game/player-save/cosmetics-progression.d.ts +0 -126
  70. package/dist/fishnet/rpc-definitions/game/player-save/cosmetics-progression.d.ts.map +0 -1
  71. package/dist/fishnet/rpc-definitions/game/player-save/debug.d.ts +0 -46
  72. package/dist/fishnet/rpc-definitions/game/player-save/debug.d.ts.map +0 -1
  73. package/dist/fishnet/rpc-definitions/game/player-save/economy-world-storage.d.ts +0 -224
  74. package/dist/fishnet/rpc-definitions/game/player-save/economy-world-storage.d.ts.map +0 -1
  75. package/dist/fishnet/rpc-definitions/game/player-save/index.d.ts +0 -1860
  76. package/dist/fishnet/rpc-definitions/game/player-save/index.d.ts.map +0 -1
  77. package/dist/fishnet/rpc-definitions/game/player-save/session-character.d.ts +0 -72
  78. package/dist/fishnet/rpc-definitions/game/player-save/session-character.d.ts.map +0 -1
  79. package/dist/fishnet/rpc-definitions/game/player-save/social-account.d.ts +0 -107
  80. package/dist/fishnet/rpc-definitions/game/player-save/social-account.d.ts.map +0 -1
  81. package/dist/fishnet/rpc-definitions/game/player-save/vending.d.ts +0 -98
  82. package/dist/fishnet/rpc-definitions/game/player-save/vending.d.ts.map +0 -1
  83. package/dist/fishnet/rpc-definitions/game/skills-component.d.ts +0 -453
  84. package/dist/fishnet/rpc-definitions/game/skills-component.d.ts.map +0 -1
  85. package/dist/fishnet/rpc-definitions/game/status-component.d.ts +0 -146
  86. package/dist/fishnet/rpc-definitions/game/status-component.d.ts.map +0 -1
  87. package/dist/fishnet/rpc-definitions/game/summoning-component.d.ts +0 -82
  88. package/dist/fishnet/rpc-definitions/game/summoning-component.d.ts.map +0 -1
  89. package/dist/fishnet/rpc-definitions/prefabs.d.ts +0 -112
  90. package/dist/fishnet/rpc-definitions/prefabs.d.ts.map +0 -1
package/dist/capture.js CHANGED
@@ -1409,7 +1409,8 @@ var playerControllerRpcs = [
1409
1409
  parameters: [
1410
1410
  {
1411
1411
  name: "obj",
1412
- typeName: "FishNet.Object.NetworkObject"
1412
+ typeName: "FishNet.Object.NetworkObject",
1413
+ codec: "networkObject"
1413
1414
  }
1414
1415
  ]
1415
1416
  },
@@ -1420,7 +1421,8 @@ var playerControllerRpcs = [
1420
1421
  parameters: [
1421
1422
  {
1422
1423
  name: "obj",
1423
- typeName: "FishNet.Object.NetworkObject"
1424
+ typeName: "FishNet.Object.NetworkObject",
1425
+ codec: "networkObject"
1424
1426
  },
1425
1427
  {
1426
1428
  name: "stats",
@@ -3122,7 +3124,8 @@ var playerControllerRpcs = [
3122
3124
  parameters: [
3123
3125
  {
3124
3126
  name: "sender",
3125
- typeName: "FishNet.Object.NetworkObject"
3127
+ typeName: "FishNet.Object.NetworkObject",
3128
+ codec: "networkObject"
3126
3129
  }
3127
3130
  ]
3128
3131
  },
@@ -3133,7 +3136,8 @@ var playerControllerRpcs = [
3133
3136
  parameters: [
3134
3137
  {
3135
3138
  name: "sender",
3136
- typeName: "FishNet.Object.NetworkObject"
3139
+ typeName: "FishNet.Object.NetworkObject",
3140
+ codec: "networkObject"
3137
3141
  }
3138
3142
  ]
3139
3143
  },
@@ -12804,7 +12808,41 @@ var playerSaveRpcs = [
12804
12808
  },
12805
12809
  {
12806
12810
  name: "list",
12807
- typeName: "CosmeticData[]"
12811
+ typeName: "CosmeticData[]",
12812
+ nullable: true,
12813
+ fields: [
12814
+ {
12815
+ name: "Rarity",
12816
+ typeName: "ItemRarity",
12817
+ codec: "packedInt32"
12818
+ },
12819
+ {
12820
+ name: "Shiny",
12821
+ typeName: "System.Boolean",
12822
+ codec: "boolean"
12823
+ },
12824
+ {
12825
+ name: "UID",
12826
+ typeName: "System.String",
12827
+ codec: "stringUtf8Packed"
12828
+ },
12829
+ {
12830
+ name: "Refine",
12831
+ typeName: "System.Int32",
12832
+ codec: "packedInt32"
12833
+ },
12834
+ {
12835
+ name: "Id",
12836
+ typeName: "System.String",
12837
+ codec: "stringUtf8Packed"
12838
+ },
12839
+ {
12840
+ name: "Favorite",
12841
+ typeName: "System.Boolean",
12842
+ codec: "boolean"
12843
+ }
12844
+ ],
12845
+ repeated: true
12808
12846
  }
12809
12847
  ]
12810
12848
  },
@@ -13116,7 +13154,8 @@ var skillsComponentRpcs = [
13116
13154
  },
13117
13155
  {
13118
13156
  name: "obj",
13119
- typeName: "FishNet.Object.NetworkObject"
13157
+ typeName: "FishNet.Object.NetworkObject",
13158
+ codec: "networkObject"
13120
13159
  },
13121
13160
  {
13122
13161
  name: "position",
@@ -13439,7 +13478,25 @@ var summoningComponentRpcs = [
13439
13478
  parameters: [
13440
13479
  {
13441
13480
  name: "data",
13442
- typeName: "SummoningComponent+SummonSkillData[]"
13481
+ typeName: "SummoningComponent+SummonSkillData[]",
13482
+ fields: [
13483
+ {
13484
+ name: "SkillId",
13485
+ typeName: "System.String",
13486
+ codec: "stringUtf8Packed"
13487
+ },
13488
+ {
13489
+ name: "Id",
13490
+ typeName: "System.String",
13491
+ codec: "stringUtf8Packed"
13492
+ },
13493
+ {
13494
+ name: "Level",
13495
+ typeName: "System.Int32",
13496
+ codec: "packedInt32"
13497
+ }
13498
+ ],
13499
+ repeated: true
13443
13500
  }
13444
13501
  ]
13445
13502
  },
@@ -13462,7 +13519,8 @@ var summoningComponentRpcs = [
13462
13519
  parameters: [
13463
13520
  {
13464
13521
  name: "obj",
13465
- typeName: "FishNet.Object.NetworkObject"
13522
+ typeName: "FishNet.Object.NetworkObject",
13523
+ codec: "networkObject"
13466
13524
  }
13467
13525
  ]
13468
13526
  },
@@ -13473,7 +13531,8 @@ var summoningComponentRpcs = [
13473
13531
  parameters: [
13474
13532
  {
13475
13533
  name: "obj",
13476
- typeName: "FishNet.Object.NetworkObject"
13534
+ typeName: "FishNet.Object.NetworkObject",
13535
+ codec: "networkObject"
13477
13536
  }
13478
13537
  ]
13479
13538
  },
@@ -13487,17 +13546,36 @@ var summoningComponentSyncTypes = [
13487
13546
  {
13488
13547
  index: 0,
13489
13548
  name: "SummonerSync",
13490
- typeName: "NetworkObject"
13549
+ typeName: "NetworkObject",
13550
+ codec: "networkObject"
13491
13551
  },
13492
13552
  {
13493
13553
  index: 1,
13494
13554
  name: "PrimarySync",
13495
- typeName: "NetworkObject"
13555
+ typeName: "NetworkObject",
13556
+ codec: "networkObject"
13496
13557
  },
13497
13558
  {
13498
13559
  index: 2,
13499
13560
  name: "SummonSkillSync",
13500
- typeName: "SummonSkillData"
13561
+ typeName: "SummonSkillData",
13562
+ fields: [
13563
+ {
13564
+ name: "SkillId",
13565
+ typeName: "System.String",
13566
+ codec: "stringUtf8Packed"
13567
+ },
13568
+ {
13569
+ name: "Id",
13570
+ typeName: "System.String",
13571
+ codec: "stringUtf8Packed"
13572
+ },
13573
+ {
13574
+ name: "Level",
13575
+ typeName: "System.Int32",
13576
+ codec: "packedInt32"
13577
+ }
13578
+ ]
13501
13579
  },
13502
13580
  {
13503
13581
  index: 3,
@@ -14043,6 +14121,10 @@ function decodeField(buffer, offset, codec) {
14043
14121
  }
14044
14122
  case "quaternion":
14045
14123
  return readFloatVector(buffer, offset, 4);
14124
+ case "networkObject": {
14125
+ const reference = readNetworkObjectReference(buffer, offset);
14126
+ return { value: reference.spawned ? reference.objectId : null, nextOffset: reference.nextOffset };
14127
+ }
14046
14128
  }
14047
14129
  }
14048
14130
 
@@ -15153,53 +15235,8 @@ function decodeFishNetBundle(payload, options = { reliable: false }) {
15153
15235
  return new FishNetSessionDecoder(options.rpcMap).decode(payload, options);
15154
15236
  }
15155
15237
 
15156
- // src/capture/adapter-selection.ts
15157
- async function resolveCaptureDevice(devices, requestedName) {
15158
- if (requestedName) {
15159
- const requested = devices.find((device) => device.name === requestedName);
15160
- if (requested)
15161
- return { device: requested, usedFallback: false };
15162
- }
15163
- const automatic = await defaultRouteDevice(devices) ?? devices.find(isUsable) ?? devices[0];
15164
- return {
15165
- device: automatic,
15166
- usedFallback: Boolean(requestedName),
15167
- ...requestedName && automatic ? { detail: "The saved adapter is unavailable; capture is using the automatically selected adapter" } : {}
15168
- };
15169
- }
15170
- function chooseDeviceByRouteOutput(devices, output) {
15171
- const routes = output.split(/\r?\n/).flatMap((line) => {
15172
- const columns = line.trim().split(/\s+/);
15173
- if (columns[0] !== "0.0.0.0" || columns[1] !== "0.0.0.0" || columns.length < 5)
15174
- return [];
15175
- const metric = Number(columns[4]);
15176
- return [{ address: columns[3], metric: Number.isFinite(metric) ? metric : Number.MAX_SAFE_INTEGER }];
15177
- }).sort((left, right) => left.metric - right.metric);
15178
- for (const route of routes) {
15179
- const match = devices.find((device) => device.addresses.includes(route.address));
15180
- if (match)
15181
- return match;
15182
- }
15183
- return;
15184
- }
15185
- async function defaultRouteDevice(devices) {
15186
- try {
15187
- const child = Bun.spawn({
15188
- cmd: ["route.exe", "PRINT", "0.0.0.0"],
15189
- stdout: "pipe",
15190
- stderr: "ignore",
15191
- windowsHide: true
15192
- });
15193
- const output = await new Response(child.stdout).text();
15194
- return await child.exited === 0 ? chooseDeviceByRouteOutput(devices, output) : undefined;
15195
- } catch {
15196
- return;
15197
- }
15198
- }
15199
- function isUsable(device) {
15200
- const label = `${device.name} ${device.description}`.toLowerCase();
15201
- return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
15202
- }
15238
+ // src/capture/win32-system.ts
15239
+ import { dlopen, FFIType } from "bun:ffi";
15203
15240
 
15204
15241
  // src/capture/ip-address.ts
15205
15242
  function formatIpv6(data) {
@@ -15227,6 +15264,194 @@ function formatIpv6(data) {
15227
15264
  return `${before}::${after}`;
15228
15265
  }
15229
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
+
15230
15455
  // src/capture/link-layer.ts
15231
15456
  var DATA_LINK = {
15232
15457
  NULL: 0,
@@ -15270,8 +15495,9 @@ function ipVersion(data) {
15270
15495
  // src/capture/npcap.ts
15271
15496
  import { existsSync } from "fs";
15272
15497
  import path from "path";
15273
- import { CString, dlopen, FFIType, read, toArrayBuffer } from "bun:ffi";
15498
+ import { CString, dlopen as dlopen2, FFIType as FFIType2, read, toArrayBuffer } from "bun:ffi";
15274
15499
  var ERROR_BUFFER_SIZE = 512;
15500
+ var PCAP_ERROR_PERM_DENIED = -8;
15275
15501
 
15276
15502
  class SystemNpcapRuntime {
15277
15503
  api;
@@ -15286,18 +15512,20 @@ class SystemNpcapRuntime {
15286
15512
  if (!existsSync(dllPath)) {
15287
15513
  return { availability: "missing", detail: "Npcap is not installed" };
15288
15514
  }
15289
- if (await isAdminOnlyInstall()) {
15290
- return {
15291
- availability: "admin-only",
15292
- detail: "Npcap is installed for administrators only; reinstall it with that restriction unchecked"
15293
- };
15294
- }
15295
15515
  try {
15296
15516
  const api = this.load();
15297
15517
  const version = String(api.symbols.pcap_lib_version());
15298
15518
  if (!version.toLowerCase().includes("npcap")) {
15299
15519
  return { availability: "error", detail: "The loaded packet capture library is not Npcap" };
15300
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
+ }
15301
15529
  this.readyStatus = { availability: "ready", detail: "Npcap is ready", version };
15302
15530
  return this.readyStatus;
15303
15531
  } catch (error) {
@@ -15308,7 +15536,9 @@ class SystemNpcapRuntime {
15308
15536
  const status = await this.status();
15309
15537
  if (status.availability !== "ready")
15310
15538
  throw new Error(status.detail);
15311
- const api = this.load();
15539
+ return this.enumerateDevices(this.load());
15540
+ }
15541
+ enumerateDevices(api) {
15312
15542
  const resultPointer = new Uint8Array(8);
15313
15543
  const errorBuffer = new Uint8Array(ERROR_BUFFER_SIZE);
15314
15544
  const result = api.symbols.pcap_findalldevs(resultPointer, errorBuffer);
@@ -15358,6 +15588,9 @@ class SystemNpcapRuntime {
15358
15588
  check(api.symbols.pcap_set_buffer_size(handle, 10 * 1024 * 1024), handle, api, "set capture buffer size");
15359
15589
  check(api.symbols.pcap_set_immediate_mode(handle, 1), handle, api, "enable immediate mode");
15360
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
+ }
15361
15594
  if (activated < 0)
15362
15595
  throw new Error(`Npcap could not activate ${device.description}: ${pcapError(api, handle)}`);
15363
15596
  const dataLink = api.symbols.pcap_datalink(handle);
@@ -15381,25 +15614,25 @@ class SystemNpcapRuntime {
15381
15614
  load() {
15382
15615
  if (this.api)
15383
15616
  return this.api;
15384
- const library = dlopen(npcapDllPath(), {
15385
- pcap_lib_version: { args: [], returns: FFIType.cstring },
15386
- pcap_findalldevs: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15387
- pcap_freealldevs: { args: [FFIType.ptr], returns: FFIType.void },
15388
- pcap_create: { args: [FFIType.cstring, FFIType.ptr], returns: FFIType.ptr },
15389
- pcap_set_snaplen: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15390
- pcap_set_promisc: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15391
- pcap_set_timeout: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15392
- pcap_set_buffer_size: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15393
- pcap_set_immediate_mode: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
15394
- pcap_activate: { args: [FFIType.ptr], returns: FFIType.i32 },
15395
- pcap_datalink: { args: [FFIType.ptr], returns: FFIType.i32 },
15396
- pcap_compile: { args: [FFIType.ptr, FFIType.ptr, FFIType.cstring, FFIType.i32, FFIType.u32], returns: FFIType.i32 },
15397
- pcap_setfilter: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15398
- pcap_freecode: { args: [FFIType.ptr], returns: FFIType.void },
15399
- pcap_setnonblock: { args: [FFIType.ptr, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
15400
- pcap_next_ex: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
15401
- pcap_geterr: { args: [FFIType.ptr], returns: FFIType.cstring },
15402
- 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 }
15403
15636
  });
15404
15637
  this.api = library;
15405
15638
  return this.api;
@@ -15493,20 +15726,6 @@ function readCStringBuffer(buffer) {
15493
15726
  function npcapDllPath() {
15494
15727
  return path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "Npcap", "wpcap.dll");
15495
15728
  }
15496
- async function isAdminOnlyInstall() {
15497
- try {
15498
- const child = Bun.spawn({
15499
- cmd: ["reg.exe", "query", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\npcap\\Parameters", "/v", "AdminOnly"],
15500
- stdout: "pipe",
15501
- stderr: "ignore",
15502
- windowsHide: true
15503
- });
15504
- const output = await new Response(child.stdout).text();
15505
- return await child.exited === 0 && /AdminOnly\s+REG_DWORD\s+0x1\b/i.test(output);
15506
- } catch {
15507
- return false;
15508
- }
15509
- }
15510
15729
  function errorMessage(error) {
15511
15730
  return error instanceof Error ? error.message : String(error);
15512
15731
  }
@@ -15532,7 +15751,7 @@ function parseIpv4(data, context) {
15532
15751
  const declaredLength = data.readUInt16BE(2);
15533
15752
  if (declaredLength < headerLength)
15534
15753
  return;
15535
- 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);
15536
15755
  }
15537
15756
  function parseIpv6(data, context) {
15538
15757
  if (data.length < 40)
@@ -15603,7 +15822,7 @@ function parseTransport(data, offset, packetEnd, truncated, protocol, ipVersion2
15603
15822
  }
15604
15823
  return;
15605
15824
  }
15606
- function formatIpv4(data, offset) {
15825
+ function formatIpv42(data, offset) {
15607
15826
  return `${data[offset]}.${data[offset + 1]}.${data[offset + 2]}.${data[offset + 3]}`;
15608
15827
  }
15609
15828
 
@@ -15623,7 +15842,7 @@ class WindowsTargetTracker {
15623
15842
  refreshing = false;
15624
15843
  published = false;
15625
15844
  lastRefreshError;
15626
- 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) {
15627
15846
  this.processName = processName;
15628
15847
  this.protocols = protocols;
15629
15848
  this.onStatus = onStatus;
@@ -15684,58 +15903,18 @@ class WindowsTargetTracker {
15684
15903
  }
15685
15904
  }
15686
15905
 
15687
- class CommandTargetSnapshotProvider {
15906
+ class WindowsTargetSnapshotProvider {
15688
15907
  async snapshot(processName, protocols) {
15689
- const process2 = Bun.spawn({
15690
- cmd: ["tasklist.exe", "/FI", `IMAGENAME eq ${processName}`, "/FO", "CSV", "/NH"],
15691
- stdout: "pipe",
15692
- stderr: "ignore",
15693
- windowsHide: true
15694
- });
15695
- const processOutput = await new Response(process2.stdout).text();
15696
- const processIds = await process2.exited === 0 ? parseTaskList(processOutput) : [];
15908
+ const processIds = findProcessIdsByName(processName);
15697
15909
  if (processIds.length === 0)
15698
15910
  return { processIds, endpoints: [] };
15699
- const outputs = await Promise.all(protocols.map(async (protocol) => {
15700
- const child = Bun.spawn({
15701
- cmd: ["netstat.exe", "-ano", "-p", protocol.toUpperCase()],
15702
- stdout: "pipe",
15703
- stderr: "ignore",
15704
- windowsHide: true
15705
- });
15706
- const output = await new Response(child.stdout).text();
15707
- return await child.exited === 0 ? parseNetstat(output, protocol) : [];
15708
- }));
15709
15911
  const selected = new Set(processIds);
15710
- 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
+ };
15711
15916
  }
15712
15917
  }
15713
- function parseTaskList(output) {
15714
- return output.split(/\r?\n/).flatMap((line) => {
15715
- const match = line.match(/^"(?:[^"]|"")*","(\d+)"/);
15716
- return match ? [Number(match[1])] : [];
15717
- }).sort((left, right) => left - right);
15718
- }
15719
- function parseNetstat(output, protocol) {
15720
- return output.split(/\r?\n/).flatMap((line) => {
15721
- const columns = line.trim().split(/\s+/);
15722
- if (columns[0]?.toLowerCase() !== protocol || columns.length < (protocol === "tcp" ? 5 : 4))
15723
- return [];
15724
- const local = parseEndpoint(columns[1]);
15725
- const processId = Number(columns[protocol === "tcp" ? 4 : 3]);
15726
- return local && Number.isInteger(processId) ? [{ protocol, ...local, processId }] : [];
15727
- });
15728
- }
15729
- function parseEndpoint(value) {
15730
- const bracketed = value.match(/^\[(.*)]:(\d+)$/);
15731
- if (bracketed)
15732
- return { address: bracketed[1], port: Number(bracketed[2]) };
15733
- const split = value.lastIndexOf(":");
15734
- if (split < 0)
15735
- return;
15736
- const port = Number(value.slice(split + 1));
15737
- return Number.isInteger(port) ? { address: value.slice(0, split), port } : undefined;
15738
- }
15739
15918
  function endpointKey(protocol, address, port) {
15740
15919
  return `${protocol}|${address}|${port}`;
15741
15920
  }