From 62c92463681365feeffe634ec7d55eb3dbcf5f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Fri, 13 Feb 2026 14:28:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=E5=8F=8D=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E5=81=9A=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/napcat-core/index.ts | 2 + .../napcat-core/packet/client/nativeClient.ts | 35 +++----- .../packet/context/clientContext.ts | 7 +- .../packet/context/napCoreContext.ts | 4 + .../packet/context/packetContext.ts | 2 +- .../packet/handler/napi2nativeLoader.ts | 79 +++++++++++++++++++ packages/napcat-framework/napcat.ts | 8 +- packages/napcat-shell/base.ts | 9 ++- 8 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 packages/napcat-core/packet/handler/napi2nativeLoader.ts diff --git a/packages/napcat-core/index.ts b/packages/napcat-core/index.ts index 5058b35e..58fa83e5 100644 --- a/packages/napcat-core/index.ts +++ b/packages/napcat-core/index.ts @@ -33,6 +33,7 @@ import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-cor import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler'; import { NTQQPacketApi } from './apis/packet'; import { NativePacketHandler } from './packet/handler/client'; +import { Napi2NativeLoader } from './packet/handler/napi2nativeLoader'; import { container, ReceiverServiceRegistry } from './packet/handler/serviceRegister'; import { appEvent } from './packet/handler/eventList'; import { TypedEventEmitter } from './packet/handler/typeEvent'; @@ -314,6 +315,7 @@ export interface InstanceContext { readonly basicInfoWrapper: QQBasicInfoWrapper; readonly pathWrapper: NapCatPathWrapper; readonly packetHandler: NativePacketHandler; + readonly napi2nativeLoader: Napi2NativeLoader; } export interface StableNTApiWrapper { diff --git a/packages/napcat-core/packet/client/nativeClient.ts b/packages/napcat-core/packet/client/nativeClient.ts index 011d18e2..99782cbc 100644 --- a/packages/napcat-core/packet/client/nativeClient.ts +++ b/packages/napcat-core/packet/client/nativeClient.ts @@ -1,11 +1,8 @@ -import path, { dirname } from 'path'; -import { fileURLToPath } from 'url'; -import fs from 'fs'; -import { constants } from 'node:os'; import { LogStack } from '@/napcat-core/packet/context/clientContext'; import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext'; import { PacketLogger } from '@/napcat-core/packet/context/loggerContext'; import { OidbPacket, PacketBuf } from '@/napcat-core/packet/transformer/base'; +import { Napi2NativeLoader } from '@/napcat-core/packet/handler/napi2nativeLoader'; export interface RecvPacket { type: string, // 仅recv data: RecvPacketData; @@ -17,48 +14,36 @@ export interface RecvPacketData { data: Buffer; } -// 0 send 1 recv -export interface NativePacketExportType { - initHook?: (send: string, recv: string) => boolean; -} - export class NativePacketClient { protected readonly napcore: NapCoreContext; protected readonly logger: PacketLogger; protected readonly cb = new Map Promise | any>(); // hash-type callback + protected readonly napi2nativeLoader: Napi2NativeLoader; logStack: LogStack; available: boolean = false; - private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; - private readonly MoeHooExport: { exports: NativePacketExportType; } = { exports: {} }; - constructor (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) { + constructor (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack, napi2nativeLoader: Napi2NativeLoader) { this.napcore = napCore; this.logger = logger; this.logStack = logStack; + this.napi2nativeLoader = napi2nativeLoader; } check (): boolean { - const platform = process.platform + '.' + process.arch; - if (!this.supportedPlatforms.includes(platform)) { - this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`); - return false; - } - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); - if (!fs.existsSync(moehoo_path)) { - this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); + if (!this.napi2nativeLoader.loaded) { + this.logStack.pushLogWarn('NativePacketClient: Napi2NativeLoader 未成功加载'); return false; } return true; } async init (_pid: number, recv: string, send: string): Promise { - const platform = process.platform + '.' + process.arch; const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild('40824'); if (isNewQQ) { - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); - process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); - this.MoeHooExport?.exports.initHook?.(send, recv); - this.available = true; + const success = this.napi2nativeLoader.initHook(send, recv); + if (success) { + this.available = true; + } } } diff --git a/packages/napcat-core/packet/context/clientContext.ts b/packages/napcat-core/packet/context/clientContext.ts index 665ebeae..5163dd92 100644 --- a/packages/napcat-core/packet/context/clientContext.ts +++ b/packages/napcat-core/packet/context/clientContext.ts @@ -2,6 +2,7 @@ import { NativePacketClient } from '@/napcat-core/packet/client/nativeClient'; import { OidbPacket } from '@/napcat-core/packet/transformer/base'; import { PacketLogger } from '@/napcat-core/packet/context/loggerContext'; import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext'; +import { Napi2NativeLoader } from '@/napcat-core/packet/handler/napi2nativeLoader'; export class LogStack { private stack: string[] = []; @@ -43,12 +44,14 @@ export class PacketClientContext { private readonly napCore: NapCoreContext; private readonly logger: PacketLogger; private readonly logStack: LogStack; + private readonly napi2nativeLoader: Napi2NativeLoader; private readonly _client: NativePacketClient; - constructor (napCore: NapCoreContext, logger: PacketLogger) { + constructor (napCore: NapCoreContext, logger: PacketLogger, napi2nativeLoader: Napi2NativeLoader) { this.napCore = napCore; this.logger = logger; this.logStack = new LogStack(logger); + this.napi2nativeLoader = napi2nativeLoader; this._client = this.newClient(); } @@ -71,7 +74,7 @@ export class PacketClientContext { private newClient (): NativePacketClient { this.logger.info('使用 NativePacketClient 作为后端'); - const client = new NativePacketClient(this.napCore, this.logger, this.logStack); + const client = new NativePacketClient(this.napCore, this.logger, this.logStack, this.napi2nativeLoader); if (!client.check()) { throw new Error('[Core] [Packet] NativePacketClient 不可用,NapCat.Packet将不会加载!'); } diff --git a/packages/napcat-core/packet/context/napCoreContext.ts b/packages/napcat-core/packet/context/napCoreContext.ts index 88420330..85b6b427 100644 --- a/packages/napcat-core/packet/context/napCoreContext.ts +++ b/packages/napcat-core/packet/context/napCoreContext.ts @@ -34,5 +34,9 @@ export class NapCoreContext { return this.core.configLoader.configData; } + get napi2nativeLoader () { + return this.core.context.napi2nativeLoader; + } + sendSsoCmdReqByContend = (cmd: string, data: Buffer) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, data); } diff --git a/packages/napcat-core/packet/context/packetContext.ts b/packages/napcat-core/packet/context/packetContext.ts index 80e4c8ae..f2cdda80 100644 --- a/packages/napcat-core/packet/context/packetContext.ts +++ b/packages/napcat-core/packet/context/packetContext.ts @@ -18,7 +18,7 @@ export class PacketContext { this.msgConverter = new PacketMsgConverter(); this.napcore = new NapCoreContext(core); this.logger = new PacketLogger(this.napcore); - this.client = new PacketClientContext(this.napcore, this.logger); + this.client = new PacketClientContext(this.napcore, this.logger, this.napcore.napi2nativeLoader); this.highway = new PacketHighwayContext(this.napcore, this.logger, this.client); this.operation = new PacketOperationContext(this); } diff --git a/packages/napcat-core/packet/handler/napi2nativeLoader.ts b/packages/napcat-core/packet/handler/napi2nativeLoader.ts new file mode 100644 index 00000000..5ec1874d --- /dev/null +++ b/packages/napcat-core/packet/handler/napi2nativeLoader.ts @@ -0,0 +1,79 @@ +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; +import { constants } from 'node:os'; +import { LogWrapper } from '../../helper/log'; + +export interface Napi2NativeExportType { + initHook?: (send: string, recv: string) => boolean; +} + +export class Napi2NativeLoader { + private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; + private readonly exports: { exports: Napi2NativeExportType; } = { exports: {} }; + protected readonly logger: LogWrapper; + private _loaded: boolean = false; + + constructor ({ logger }: { logger: LogWrapper; }) { + this.logger = logger; + this.load(); + } + + private load (): void { + const platform = process.platform + '.' + process.arch; + + if (!this.supportedPlatforms.includes(platform)) { + this.logger.logWarn(`Napi2NativeLoader: 不支持的平台: ${platform}`); + this._loaded = false; + return; + } + + const nativeModulePath = path.join( + dirname(fileURLToPath(import.meta.url)), + './native/napi2native/napi2native.' + platform + '.node' + ); + + if (!fs.existsSync(nativeModulePath)) { + this.logger.logWarn(`Napi2NativeLoader: 缺失运行时文件: ${nativeModulePath}`); + this._loaded = false; + return; + } + + try { + process.dlopen(this.exports, nativeModulePath, constants.dlopen.RTLD_LAZY); + this._loaded = true; + this.logger.log('[Napi2NativeLoader] 加载成功'); + } catch (error) { + this.logger.logError('Napi2NativeLoader 加载出错:', error); + this._loaded = false; + } + } + + get loaded (): boolean { + return this._loaded; + } + + get nativeExports (): Napi2NativeExportType { + return this.exports.exports; + } + + /** + * 初始化 Hook + * @param send send 偏移地址 + * @param recv recv 偏移地址 + * @returns 是否初始化成功 + */ + initHook (send: string, recv: string): boolean { + if (!this._loaded) { + this.logger.logWarn('Napi2NativeLoader 未成功加载,无法初始化 Hook'); + return false; + } + + try { + return this.nativeExports.initHook?.(send, recv) ?? false; + } catch (error) { + this.logger.logError('Napi2NativeLoader initHook 出错:', error); + return false; + } + } +} diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index 5a1c1d3d..e8c85172 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -2,6 +2,7 @@ import { NapCatPathWrapper } from 'napcat-common/src/path'; import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index'; import { NapCatAdapterManager } from 'napcat-adapter'; import { NativePacketHandler } from 'napcat-core/packet/handler/client'; +import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader'; import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg'; import { logSubscription, LogWrapper } from 'napcat-core/helper/log'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; @@ -40,6 +41,7 @@ export async function NCoreInitFramework ( const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const wrapper = loadQQWrapper(basicInfoWrapper.QQMainPath, basicInfoWrapper.getFullQQVersion()); const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 + const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用 // nativePacketHandler.onAll((packet) => { // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); // }); @@ -73,7 +75,7 @@ export async function NCoreInitFramework ( // 过早进入会导致addKernelMsgListener等Listener添加失败 // await sleep(2500); // 初始化 NapCatFramework - const loaderObject = new NapCatFramework(wrapper, session, logger, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler); + const loaderObject = new NapCatFramework(wrapper, session, logger, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler, napi2nativeLoader); await loaderObject.core.initCore(); // 启动WebUi @@ -101,10 +103,12 @@ export class NapCatFramework { selfInfo: SelfInfo, basicInfoWrapper: QQBasicInfoWrapper, pathWrapper: NapCatPathWrapper, - packetHandler: NativePacketHandler + packetHandler: NativePacketHandler, + napi2nativeLoader: Napi2NativeLoader ) { this.context = { packetHandler, + napi2nativeLoader, workingEnv: NapCatCoreWorkingEnv.Framework, wrapper, session, diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index 357da189..f84979b3 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -30,6 +30,7 @@ import { NodeIO3MiscListener } from 'napcat-core/listeners/NodeIO3MiscListener'; import { sleep } from 'napcat-common/src/helper'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg'; import { NativePacketHandler } from 'napcat-core/packet/handler/client'; +import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader'; import { logSubscription, LogWrapper } from '@/napcat-core/helper/log'; import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; @@ -396,6 +397,7 @@ export async function NCoreInitShell () { const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const wrapper = loadQQWrapper(basicInfoWrapper.QQMainPath, basicInfoWrapper.getFullQQVersion()); const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 + const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用 // nativePacketHandler.onAll((packet) => { // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); @@ -488,7 +490,8 @@ export async function NCoreInitShell () { selfInfo, basicInfoWrapper, pathWrapper, - nativePacketHandler + nativePacketHandler, + napi2nativeLoader ).InitNapCat(); } @@ -503,10 +506,12 @@ export class NapCatShell { selfInfo: SelfInfo, basicInfoWrapper: QQBasicInfoWrapper, pathWrapper: NapCatPathWrapper, - packetHandler: NativePacketHandler + packetHandler: NativePacketHandler, + napi2nativeLoader: Napi2NativeLoader ) { this.context = { packetHandler, + napi2nativeLoader, workingEnv: NapCatCoreWorkingEnv.Shell, wrapper, session,