@napuketto/loader 0.0.37 → 0.0.39

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.
@@ -793,6 +793,63 @@ const IpcMessageSchema = zod.z.discriminatedUnion("type", [
793
793
  IpcControlMessageSchema
794
794
  ]);
795
795
  //#endregion
796
+ //#region src/host/ipc/json-safe.ts
797
+ /**
798
+ * json-safe.ts:IPC 出站负载 JSON 安全化(2026-10-24)。
799
+ *
800
+ * `JSON.stringify` 对 Map/Set 内容全丢(Map → `{}`、Set → `[]`):
801
+ * kernel 事件 args(RawMessage.videoElement.thumbPath `Map<0, path>`)与
802
+ * 动作结果(如 GroupApi.uinToUid 的 `Map<qq, uid>`)跨进程后全部退化为
803
+ * 空对象——koishi 插件侧读 `thumbPath.get(0)` 直接 TypeError,整条视频
804
+ * 消息事件分派失败(2026-10-24 线上报)。编码前深规整:
805
+ * - Map → 平面对象(Object.fromEntries,数字 key 变字符串 key)
806
+ * - Set → 数组
807
+ * - 普通对象/数组递归;非普通对象(Date/Buffer/类实例)原样交 stringify
808
+ * (与既有行为一致,不引入新语义)
809
+ * 幂等:已规整的负载再跑一遍结果不变。
810
+ */
811
+ function toJsonSafe(value, ancestors = []) {
812
+ if (value === null || typeof value !== "object") return value;
813
+ if (ancestors.includes(value)) return;
814
+ const nested = [...ancestors, value];
815
+ if (value instanceof Map) return toJsonSafe(Object.fromEntries(value), nested);
816
+ if (value instanceof Set) return Array.from(value, (item) => toJsonSafe(item, nested));
817
+ if (Array.isArray(value)) return value.map((item) => toJsonSafe(item, nested));
818
+ const proto = Object.getPrototypeOf(value);
819
+ if (proto !== Object.prototype && proto !== null) return value;
820
+ const out = {};
821
+ for (const [key, val] of Object.entries(value)) {
822
+ const safe = toJsonSafe(val, nested);
823
+ if (safe !== void 0) out[key] = safe;
824
+ }
825
+ return out;
826
+ }
827
+ //#endregion
828
+ //#region src/host/ipc/ipc-codec.ts
829
+ /**
830
+ * ipc-codec.ts:IPC 消息编解码(JSON 行协议,与 koishi 插件侧 src/ipc/codec.ts 对齐)。
831
+ *
832
+ * 每行一条 JSON 消息,`\n` 结尾。解码用 IpcMessageSchema.safeParse 校验:
833
+ * 非法行 / 空行 / v 不匹配 / type 未知 / payload 形状非法 → null,调用方忽略,
834
+ * 不崩通道。
835
+ */
836
+ /** 编码为 JSON 行(追加换行)。Map/Set 先规整为可序列化形态(见 json-safe.ts)。 */
837
+ function encodeIpcMessage(message) {
838
+ return `${JSON.stringify(toJsonSafe(message))}\n`;
839
+ }
840
+ /** 解码一行 JSON;非法行/空行/形状不合法返回 null。 */
841
+ function decodeIpcMessage(line) {
842
+ const trimmed = line.trim();
843
+ if (trimmed === "") return null;
844
+ try {
845
+ const parsed = JSON.parse(trimmed);
846
+ const result = IpcMessageSchema.safeParse(parsed);
847
+ return result.success ? result.data : null;
848
+ } catch {
849
+ return null;
850
+ }
851
+ }
852
+ //#endregion
796
853
  //#region src/host/ipc/ipc-sender.ts
797
854
  /**
798
855
  * ipc-sender.ts:IPC 消息发送封装(self-host 子进程 → koishi 插件)。
@@ -814,7 +871,7 @@ function enableIpc() {
814
871
  function sendIpc(message) {
815
872
  if (!enabled) return;
816
873
  try {
817
- process.stdout.write(`${JSON.stringify(message)}\n`);
874
+ process.stdout.write(encodeIpcMessage(message));
818
875
  } catch {}
819
876
  }
820
877
  /** 引导状态(booting/dlopening/logging/sessioning/ready/failed)。 */
@@ -956,27 +1013,6 @@ function attachIpcServices(actions, services) {
956
1013
  };
957
1014
  }
958
1015
  //#endregion
959
- //#region src/host/ipc/ipc-codec.ts
960
- /**
961
- * ipc-codec.ts:IPC 消息编解码(JSON 行协议,与 koishi 插件侧 src/ipc/codec.ts 对齐)。
962
- *
963
- * 每行一条 JSON 消息,`\n` 结尾。解码用 IpcMessageSchema.safeParse 校验:
964
- * 非法行 / 空行 / v 不匹配 / type 未知 / payload 形状非法 → null,调用方忽略,
965
- * 不崩通道。
966
- */
967
- /** 解码一行 JSON;非法行/空行/形状不合法返回 null。 */
968
- function decodeIpcMessage(line) {
969
- const trimmed = line.trim();
970
- if (trimmed === "") return null;
971
- try {
972
- const parsed = JSON.parse(trimmed);
973
- const result = IpcMessageSchema.safeParse(parsed);
974
- return result.success ? result.data : null;
975
- } catch {
976
- return null;
977
- }
978
- }
979
- //#endregion
980
1016
  //#region src/host/ipc/ipc-ob11.ts
981
1017
  /**
982
1018
  * ipc-ob11.ts:OB11 动作容器 IPC 桥(NAPUTO_IPC=1 可选装配,2026-08-27)。
@@ -0,0 +1,32 @@
1
+ # NOTICE — 本目录的 QQNT.dll 是自研符号转发 stub,非腾讯原始二进制
2
+
3
+ 本文件(约 70 KB)由 NapukettoQQ 项目自研编译,用于「标准 Node + stub 转发」的自建宿主
4
+ 模式:让 wrapper.node 在纯 Node 环境下经 process.dlopen 加载。它不是、也不包含腾讯 QQ
5
+ 客户端的 QQNT.dll——官方组件为数十 MB 的完整客户端二进制,本目录不含任何腾讯代码字节。
6
+
7
+ ## 成分
8
+
9
+ - 99 个导出中 97 个为 PE Export Forwarder,转发到 node.exe 的既有实现;符号名为
10
+ Node-API(`napi_*`)、libuv(`uv_*`)与 V8 的公开接口名(Node.js 为 OpenJS 基金会
11
+ MIT 许可项目),另含 1 个别名 `qq_magic_napi_register`(转发至 node.exe 的
12
+ `napi_module_register`)。
13
+ - 2 个自研空实现:`IsEnvironmentStopping` / `PerfTrace`(见本仓库 `stub/stub-qqnt.cpp`,
14
+ 各数行)。
15
+ - 符号清单来自对 wrapper.node PE 导入表的分析(互操作接口事实)。文件命名为
16
+ `QQNT.dll` 是 Windows PE 加载器按导入表模块名解析的机械要求。
17
+
18
+ ## 来源与边界
19
+
20
+ - 编译源为本仓库(闭源)`stub/stub-qqnt.cpp` + `stub/stub-qqnt.def`
21
+ (llvm-mingw:`clang++ -shared stub-qqnt.cpp stub-qqnt.def -o QQNT.dll`)。
22
+ - 运行形态:独立 DLL 置于 PATH 前置目录,不修改 QQ 安装目录任何文件、不注入官方进程。
23
+ - 本目录随 npm 包 `@napuketto/loader` 的 `files` 字段分发。
24
+
25
+ ---
26
+
27
+ This directory ships a self-authored PE export-forwarding stub (≈70 KB) compiled by the
28
+ NapukettoQQ project. It is not, and contains no code from, Tencent's original QQNT.dll. 97 of
29
+ its 99 exports are forwarders to node.exe (Node-API / libuv / V8 public symbols); the
30
+ remaining 2 are trivial stubs written in this repository. The symbol list is derived from the
31
+ PE import table of wrapper.node (interoperability facts), and the file must be named
32
+ QQNT.dll to satisfy the Windows PE loader's module-name resolution.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napuketto/loader",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "NapukettoQQ 引导组件:自建宿主(标准 Node + stub QQNT.dll)dlopen wrapper.node 并启动 kernel,不拉起 QQ / 不注入",
5
5
  "type": "module",
6
6
  "files": [