@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.
- package/README.md +20 -20
- package/dist/capture/adapter-selection.d.ts +1 -1
- package/dist/capture/adapter-selection.d.ts.map +1 -1
- package/dist/capture/npcap.d.ts +2 -1
- package/dist/capture/npcap.d.ts.map +1 -1
- package/dist/capture/target-tracker.d.ts +2 -8
- package/dist/capture/target-tracker.d.ts.map +1 -1
- package/dist/capture/win32-system.d.ts +11 -0
- package/dist/capture/win32-system.d.ts.map +1 -0
- package/dist/capture.js +328 -149
- package/dist/fishnet/builtin-maps.d.ts +63 -0
- package/dist/fishnet/builtin-maps.d.ts.map +1 -1
- package/dist/fishnet/definitions/codecs.d.ts +1 -1
- package/dist/fishnet/definitions/codecs.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/generated/index.d.ts +63 -0
- package/dist/fishnet/rpc-definitions/generated/index.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/generated/player-controller.d.ts +4 -0
- package/dist/fishnet/rpc-definitions/generated/player-controller.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/generated/player-save.d.ts +27 -0
- package/dist/fishnet/rpc-definitions/generated/player-save.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/generated/skills-component.d.ts +1 -0
- package/dist/fishnet/rpc-definitions/generated/skills-component.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/generated/summoning-component.d.ts +31 -0
- package/dist/fishnet/rpc-definitions/generated/summoning-component.d.ts.map +1 -1
- package/dist/fishnet/rpc-definitions/index.d.ts +63 -0
- package/dist/fishnet/rpc-definitions/index.d.ts.map +1 -1
- package/dist/index.js +94 -12
- package/package.json +9 -9
- package/dist/fishnet/rpc-definitions/broadcasts.d.ts +0 -23
- package/dist/fishnet/rpc-definitions/broadcasts.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/current-build.d.ts +0 -8501
- package/dist/fishnet/rpc-definitions/current-build.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/fishnet/network-animator.d.ts +0 -42
- package/dist/fishnet/rpc-definitions/fishnet/network-animator.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/fishnet/network-transform.d.ts +0 -138
- package/dist/fishnet/rpc-definitions/fishnet/network-transform.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/fishnet/predicted-owner.d.ts +0 -26
- package/dist/fishnet/rpc-definitions/fishnet/predicted-owner.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/base-unit-controller.d.ts +0 -122
- package/dist/fishnet/rpc-definitions/game/base-unit-controller.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/combat-component.d.ts +0 -44
- package/dist/fishnet/rpc-definitions/game/combat-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/health-component.d.ts +0 -306
- package/dist/fishnet/rpc-definitions/game/health-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/loot-drop.d.ts +0 -104
- package/dist/fishnet/rpc-definitions/game/loot-drop.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/monster-controller.d.ts +0 -144
- package/dist/fishnet/rpc-definitions/game/monster-controller.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/move-component.d.ts +0 -74
- package/dist/fishnet/rpc-definitions/game/move-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/chat-presence.d.ts +0 -332
- package/dist/fishnet/rpc-definitions/game/player-controller/chat-presence.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/endgame-pvp.d.ts +0 -89
- package/dist/fishnet/rpc-definitions/game/player-controller/endgame-pvp.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/guild.d.ts +0 -393
- package/dist/fishnet/rpc-definitions/game/player-controller/guild.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/index.d.ts +0 -23
- package/dist/fishnet/rpc-definitions/game/player-controller/index.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/party.d.ts +0 -168
- package/dist/fishnet/rpc-definitions/game/player-controller/party.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/trade-market.d.ts +0 -232
- package/dist/fishnet/rpc-definitions/game/player-controller/trade-market.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-controller/traversal-session.d.ts +0 -58
- package/dist/fishnet/rpc-definitions/game/player-controller/traversal-session.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/character-build.d.ts +0 -266
- package/dist/fishnet/rpc-definitions/game/player-save/character-build.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/character-data.d.ts +0 -991
- package/dist/fishnet/rpc-definitions/game/player-save/character-data.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/cosmetics-progression.d.ts +0 -126
- package/dist/fishnet/rpc-definitions/game/player-save/cosmetics-progression.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/debug.d.ts +0 -46
- package/dist/fishnet/rpc-definitions/game/player-save/debug.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/economy-world-storage.d.ts +0 -224
- package/dist/fishnet/rpc-definitions/game/player-save/economy-world-storage.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/index.d.ts +0 -1860
- package/dist/fishnet/rpc-definitions/game/player-save/index.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/session-character.d.ts +0 -72
- package/dist/fishnet/rpc-definitions/game/player-save/session-character.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/social-account.d.ts +0 -107
- package/dist/fishnet/rpc-definitions/game/player-save/social-account.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/player-save/vending.d.ts +0 -98
- package/dist/fishnet/rpc-definitions/game/player-save/vending.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/skills-component.d.ts +0 -453
- package/dist/fishnet/rpc-definitions/game/skills-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/status-component.d.ts +0 -146
- package/dist/fishnet/rpc-definitions/game/status-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/game/summoning-component.d.ts +0 -82
- package/dist/fishnet/rpc-definitions/game/summoning-component.d.ts.map +0 -1
- package/dist/fishnet/rpc-definitions/prefabs.d.ts +0 -112
- 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/
|
|
15157
|
-
|
|
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
|
-
|
|
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 =
|
|
15385
|
-
pcap_lib_version: { args: [], returns:
|
|
15386
|
-
pcap_findalldevs: { args: [
|
|
15387
|
-
pcap_freealldevs: { args: [
|
|
15388
|
-
pcap_create: { args: [
|
|
15389
|
-
pcap_set_snaplen: { args: [
|
|
15390
|
-
pcap_set_promisc: { args: [
|
|
15391
|
-
pcap_set_timeout: { args: [
|
|
15392
|
-
pcap_set_buffer_size: { args: [
|
|
15393
|
-
pcap_set_immediate_mode: { args: [
|
|
15394
|
-
pcap_activate: { args: [
|
|
15395
|
-
pcap_datalink: { args: [
|
|
15396
|
-
pcap_compile: { args: [
|
|
15397
|
-
pcap_setfilter: { args: [
|
|
15398
|
-
pcap_freecode: { args: [
|
|
15399
|
-
pcap_setnonblock: { args: [
|
|
15400
|
-
pcap_next_ex: { args: [
|
|
15401
|
-
pcap_geterr: { args: [
|
|
15402
|
-
pcap_close: { args: [
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
15906
|
+
class WindowsTargetSnapshotProvider {
|
|
15688
15907
|
async snapshot(processName, protocols) {
|
|
15689
|
-
const
|
|
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 {
|
|
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
|
}
|