26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
442 lines
19 KiB
TypeScript
442 lines
19 KiB
TypeScript
import { SnowCatPathWrapper } from 'snowcat-common/src/path';
|
|
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'snowcat-webui-backend/index';
|
|
import { SnowCatAdapterManager } from 'snowcat-adapter';
|
|
import { NativePacketHandler } from 'snowcat-core/packet/handler/client';
|
|
import { Napi2NativeLoader } from 'snowcat-core/packet/handler/napi2nativeLoader';
|
|
import { loadSnowCatConfig } from '@/snowcat-core/helper/config';
|
|
import { FFmpegService } from 'snowcat-core/helper/ffmpeg/ffmpeg';
|
|
import { logSubscription, LogWrapper } from 'snowcat-core/helper/log';
|
|
import { QQBasicInfoWrapper } from '@/snowcat-core/helper/qq-basic-info';
|
|
import { InstanceContext, loadQQWrapper, SnowCatCore, SnowCatCoreWorkingEnv, NodeIKernelLoginListener, NodeIKernelLoginService, NodeIQQNTWrapperSession, SelfInfo, WrapperNodeApi } from '@/snowcat-core';
|
|
import { proxiedListenerOf } from '@/snowcat-core/helper/proxy-handler';
|
|
import { statusHelperSubscription } from '@/snowcat-core/helper/status';
|
|
import { applyPendingUpdates } from '@/snowcat-webui-backend/src/api/UpdateNapCat';
|
|
import { WebUiDataRuntime } from '@/snowcat-webui-backend/src/helper/Data';
|
|
import { NapProtoMsg } from 'snowcat-protobuf';
|
|
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcp0XCDE_2RespBody } from '@/snowcat-core/packet/transformer/proto';
|
|
|
|
// Framework ES入口文件
|
|
export async function getWebUiUrl () {
|
|
const WebUiConfigData = (await WebUiConfig.GetWebUIConfig());
|
|
return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + encodeURIComponent(WebUiConfigData.token);
|
|
}
|
|
|
|
export async function NCoreInitFramework (
|
|
session: NodeIQQNTWrapperSession,
|
|
loginService: NodeIKernelLoginService,
|
|
registerInitCallback: (callback: () => void) => void
|
|
) {
|
|
// 在进入本层前是否登录未进行判断
|
|
console.log('SnowCat Framework App Loading...');
|
|
|
|
process.on('uncaughtException', (err) => {
|
|
console.log('[SnowCat] [Error] Unhandled Exception:', err.message);
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason) => {
|
|
console.log('[SnowCat] [Error] unhandledRejection:', reason);
|
|
});
|
|
|
|
const pathWrapper = new SnowCatPathWrapper();
|
|
|
|
const logger = new LogWrapper(pathWrapper.logsPath);
|
|
await applyPendingUpdates(pathWrapper, logger);
|
|
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
|
const wrapper = loadQQWrapper(basicInfoWrapper.QQMainPath, basicInfoWrapper.getFullQQVersion());
|
|
const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用
|
|
const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用
|
|
const snowcatConfig = loadSnowCatConfig(pathWrapper.configPath);
|
|
// console.log('[SnowCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.());
|
|
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
|
|
const bypassOptions = snowcatConfig.bypass ?? {};
|
|
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
|
|
if (bypassEnabled) {
|
|
logger.log('[SnowCat] Napi2NativeLoader: 已启用Bypass');
|
|
}
|
|
logger.log('[SnowCat] Napi2NativeLoader: Framework模式Bypass配置:', bypassOptions);
|
|
} else {
|
|
logger.log('[SnowCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
|
|
}
|
|
// nativePacketHandler.onAll((packet) => {
|
|
// console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data);
|
|
// });
|
|
await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion(), snowcatConfig.o3HookMode === 1);
|
|
// 在 init 之后注册监听器
|
|
|
|
// 登录前监听 OidbSvcTrpcTcp.0xcde_2 数据包,获取数据库 passphrase
|
|
let dbPassphrase: string | undefined;
|
|
nativePacketHandler.onCmd('OidbSvcTrpcTcp.0xcde_2', ({ type, hex_data }) => {
|
|
if (type !== 1) return; // 仅处理接收方向
|
|
try {
|
|
const raw = Buffer.from(hex_data, 'hex');
|
|
const base = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(raw);
|
|
if (base.body && base.body.length > 0) {
|
|
const body = new NapProtoMsg(OidbSvcTrpcTcp0XCDE_2RespBody).decode(base.body);
|
|
if (body.inner?.value) {
|
|
dbPassphrase = body.inner.value;
|
|
logger.log('[SnowCat] 已启用数据库辅助支持能力');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
logger.logError('[SnowCat] [0xCDE_2] 解析失败:', e);
|
|
}
|
|
});
|
|
|
|
// 初始化 FFmpeg 服务
|
|
await FFmpegService.init(pathWrapper.binaryPath, logger);
|
|
|
|
// 提前启动 WebUI,使其在登录前可用,支持通过 WebUI 操控登录过程
|
|
WebUiDataRuntime.setWorkingEnv(SnowCatCoreWorkingEnv.Framework);
|
|
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
|
|
|
|
// 直到登录成功后,执行下一步
|
|
const selfInfo = await new Promise<SelfInfo>((resolve) => {
|
|
const loginContext = { isLogined: false };
|
|
|
|
const loginListener = new NodeIKernelLoginListener();
|
|
|
|
loginListener.onUserLoggedIn = (userid: string) => {
|
|
const tips = `当前账号(${userid})已登录,无法重复登录`;
|
|
logger.logError(tips);
|
|
WebUiDataRuntime.setQQLoginError(tips);
|
|
};
|
|
|
|
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
|
|
loginContext.isLogined = true;
|
|
WebUiDataRuntime.setQQLoginStatus(true);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
const oneBotContext = WebUiDataRuntime.getOneBotContext();
|
|
if (oneBotContext?.core?.selfInfo) {
|
|
oneBotContext.core.selfInfo.online = true;
|
|
WebUiDataRuntime.setQQLoginPhase('ready');
|
|
} else {
|
|
WebUiDataRuntime.setQQLoginPhase('initializing');
|
|
}
|
|
await new Promise<void>(resolve => {
|
|
registerInitCallback(() => resolve());
|
|
});
|
|
resolve({
|
|
uid: loginResult.uid,
|
|
uin: loginResult.uin,
|
|
nick: '', // 获取不到
|
|
online: true,
|
|
});
|
|
};
|
|
|
|
loginListener.onQRCodeGetPicture = ({ qrcodeUrl }) => {
|
|
WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
WebUiDataRuntime.setQQLoginPhase('waiting_qrcode');
|
|
logger.log('[SnowCat] [Framework] 二维码已更新, URL:', qrcodeUrl);
|
|
};
|
|
|
|
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => {
|
|
if (!loginContext.isLogined) {
|
|
logger.logError('[SnowCat] [Framework] [Login] QRCode Session Failed, ErrType:', errType, 'ErrCode:', errCode);
|
|
if (errType === 1 && errCode === 3) {
|
|
WebUiDataRuntime.setQQLoginError('二维码已过期,请刷新');
|
|
}
|
|
}
|
|
};
|
|
|
|
loginListener.onLoginFailed = (...args) => {
|
|
const errInfo = JSON.stringify(args);
|
|
logger.logError('[SnowCat] [Framework] [Login] Login Failed, ErrInfo:', errInfo);
|
|
WebUiDataRuntime.setQQLoginError(`登录失败: ${errInfo}`);
|
|
};
|
|
|
|
loginListener.onLoginConnected = () => {
|
|
logger.log('[SnowCat] [Framework] 登录服务已连接');
|
|
// 注册 WebUI 登录操作回调
|
|
registerWebUiLoginCallbacks(loginService, loginContext, logger);
|
|
// 获取历史登录列表供 WebUI 使用
|
|
loginService.getLoginList().then((res) => {
|
|
const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin);
|
|
WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString()));
|
|
WebUiDataRuntime.setQQNewLoginList(list);
|
|
}).catch(e => logger.logError('[SnowCat] [Framework] 获取登录列表失败:', e));
|
|
if (!loginContext.isLogined) {
|
|
const requested = loginService.getQRCodePicture();
|
|
logger.logWarn('[SnowCat] [Framework] 登录服务连接完成,正在获取二维码', { requested, msfStatus: loginService.getMsfStatus() });
|
|
}
|
|
};
|
|
|
|
loginListener.onQRCodeSessionUserScaned = () => {
|
|
WebUiDataRuntime.setQQLoginPhase('qrcode_scanned');
|
|
logger.log('[SnowCat] [Framework] 二维码已被扫描,等待确认...');
|
|
};
|
|
|
|
loginListener.onPasswordLoginFailed = (...args) => {
|
|
logger.logError('[SnowCat] [Framework] 密码登录失败:', ...args);
|
|
};
|
|
|
|
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
|
});
|
|
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
|
// await sleep(2500);
|
|
// 初始化 SnowCatFramework
|
|
const loaderObject = new SnowCatFramework(wrapper, session, logger, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler, napi2nativeLoader);
|
|
// 将捕获的 passphrase 设置到 core 上,供 DatabaseApi 使用
|
|
if (dbPassphrase) {
|
|
loaderObject.core.dbPassphrase = dbPassphrase;
|
|
}
|
|
await loaderObject.core.initCore();
|
|
|
|
// 登录成功后设置数据路径
|
|
WebUiDataRuntime.setQQDataPath(loaderObject.core.dataPath);
|
|
|
|
// // 测试 DatabaseApi
|
|
// if (dbPassphrase) {
|
|
// const dbApi = loaderObject.core.apis.DatabaseApi;
|
|
// const sqliteOk = await dbApi.isSqliteAvailable();
|
|
// if (sqliteOk) {
|
|
// try {
|
|
// const result = dbApi.readDatabase('nt_msg.db');
|
|
// if (result) {
|
|
// logger.log('[SnowCat] [Database] nt_msg.db:\n' + dbApi.formatResults([result]));
|
|
// }
|
|
// } catch (e) {
|
|
// logger.logError('[SnowCat] [Database] 读取失败:', e);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// 使用 SnowCatAdapterManager 统一管理协议适配器
|
|
const adapterManager = new SnowCatAdapterManager(loaderObject.core, loaderObject.context, pathWrapper);
|
|
await adapterManager.initAdapters();
|
|
// 注册 OneBot 适配器到 WebUiDataRuntime,供调试功能使用
|
|
const oneBotAdapter = adapterManager.getOneBotAdapter();
|
|
if (oneBotAdapter) {
|
|
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
|
if (WebUiDataRuntime.getQQLoginStatus()) {
|
|
WebUiDataRuntime.setQQLoginPhase('ready');
|
|
}
|
|
}
|
|
|
|
// 监听下线通知并同步到 WebUI(兜底保障,防止 WebUI 状态不同步)
|
|
let restartRequested = false;
|
|
loaderObject.core.event.on('KickedOffLine', (tips: string) => {
|
|
WebUiDataRuntime.setQQLoginStatus(false);
|
|
WebUiDataRuntime.setQQLoginQrcodeURL('');
|
|
WebUiDataRuntime.setQQLoginError(tips);
|
|
WebUiDataRuntime.setQQLoginPhase('reconnecting');
|
|
if (restartRequested) return;
|
|
restartRequested = true;
|
|
setTimeout(() => {
|
|
WebUiDataRuntime.requestRestartProcess().then((restartResult) => {
|
|
logger.logWarn('[SnowCat] [Framework] 账号被踢下线,正在重启 Worker 以重新创建 QQ 登录服务', restartResult);
|
|
if (!restartResult.result) {
|
|
restartRequested = false;
|
|
WebUiDataRuntime.setQQLoginPhase('offline');
|
|
WebUiDataRuntime.setQQLoginError(`${tips}\n登录服务重启失败:${restartResult.message}`);
|
|
}
|
|
}).catch((error) => {
|
|
restartRequested = false;
|
|
WebUiDataRuntime.setQQLoginPhase('offline');
|
|
WebUiDataRuntime.setQQLoginError(`${tips}\n登录服务重启失败:${(error as Error).message}`);
|
|
});
|
|
}, 3_500);
|
|
});
|
|
}
|
|
|
|
function registerWebUiLoginCallbacks (
|
|
loginService: NodeIKernelLoginService,
|
|
loginContext: { isLogined: boolean; },
|
|
logger: LogWrapper
|
|
) {
|
|
// 刷新二维码
|
|
WebUiDataRuntime.setRefreshQRCodeCallback(async () => {
|
|
loginContext.isLogined = false;
|
|
const msfStatus = loginService.getMsfStatus();
|
|
if (msfStatus === 2) {
|
|
WebUiDataRuntime.setQQLoginPhase('reconnecting');
|
|
const connected = loginService.connect();
|
|
logger.logWarn('[SnowCat] [Framework] 登录服务离线,正在重连以获取二维码', { connected, msfStatus });
|
|
if (!connected) {
|
|
throw new Error('QQ 登录服务无法重新连接');
|
|
}
|
|
return;
|
|
}
|
|
const requested = loginService.getQRCodePicture();
|
|
logger.logWarn('[SnowCat] [Framework] 请求刷新二维码', { requested, msfStatus });
|
|
if (!requested) {
|
|
throw new Error('QQ 未接受二维码刷新请求');
|
|
}
|
|
});
|
|
|
|
// 快速登录
|
|
WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
|
|
return await new Promise((resolve) => {
|
|
if (!uin) {
|
|
return resolve({ result: false, message: '快速登录失败' });
|
|
}
|
|
logger.log('[SnowCat] [Framework] 正在快速登录', uin);
|
|
loginService.quickLoginWithUin(uin).then(res => {
|
|
const success = res.result === '0' && !res.loginErrorInfo?.errMsg;
|
|
if (!success) {
|
|
const errMsg = res.loginErrorInfo?.errMsg || `快速登录失败,错误码: ${res.result}`;
|
|
WebUiDataRuntime.setQQLoginError(errMsg);
|
|
resolve({ result: false, message: errMsg });
|
|
} else {
|
|
loginContext.isLogined = true;
|
|
WebUiDataRuntime.setQQLoginStatus(true);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
resolve({ result: true, message: '' });
|
|
}
|
|
}).catch((e) => {
|
|
logger.logError('[SnowCat] [Framework] 快速登录异常:', e);
|
|
WebUiDataRuntime.setQQLoginError('快速登录发生错误');
|
|
resolve({ result: false, message: '快速登录发生错误' });
|
|
});
|
|
});
|
|
});
|
|
|
|
// 密码登录
|
|
WebUiDataRuntime.setPasswordLoginCall(async (uin: string, passwordMd5: string) => {
|
|
return await new Promise((resolve) => {
|
|
if (!uin || !passwordMd5) {
|
|
return resolve({ result: false, message: '密码登录失败:参数不完整' });
|
|
}
|
|
logger.log('[SnowCat] [Framework] 正在密码登录', uin);
|
|
loginService.passwordLogin({
|
|
uin,
|
|
passwordMd5,
|
|
step: 0,
|
|
newDeviceLoginSig: new Uint8Array(),
|
|
proofWaterSig: new Uint8Array(),
|
|
proofWaterRand: new Uint8Array(),
|
|
proofWaterSid: new Uint8Array(),
|
|
unusualDeviceCheckSig: new Uint8Array(),
|
|
}).then(res => {
|
|
if (res.result === '140022008') {
|
|
const proofWaterUrl = res.loginErrorInfo?.proofWaterUrl || '';
|
|
resolve({ result: false, message: '需要验证码', needCaptcha: true, proofWaterUrl });
|
|
} else if (res.result === '140022010' || res.result === '140022011') {
|
|
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
|
|
const newDevicePullQrCodeSig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
|
|
resolve({ result: false, message: '新设备需要扫码验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig });
|
|
} else if (res.result !== '0') {
|
|
const errMsg = res.loginErrorInfo?.errMsg || '密码登录失败';
|
|
WebUiDataRuntime.setQQLoginError(errMsg);
|
|
resolve({ result: false, message: errMsg });
|
|
} else {
|
|
loginContext.isLogined = true;
|
|
WebUiDataRuntime.setQQLoginStatus(true);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
resolve({ result: true, message: '' });
|
|
}
|
|
}).catch((e) => {
|
|
logger.logError('[SnowCat] [Framework] 密码登录异常:', e);
|
|
WebUiDataRuntime.setQQLoginError('密码登录发生错误');
|
|
resolve({ result: false, message: '密码登录发生错误' });
|
|
});
|
|
});
|
|
});
|
|
|
|
// 验证码登录(密码登录需要验证码时的第二步)
|
|
WebUiDataRuntime.setCaptchaLoginCall(async (uin: string, passwordMd5: string, ticket: string, randstr: string, sid: string) => {
|
|
return await new Promise((resolve) => {
|
|
if (!uin || !passwordMd5 || !ticket) {
|
|
return resolve({ result: false, message: '验证码登录失败:参数不完整' });
|
|
}
|
|
logger.log('[SnowCat] [Framework] 正在验证码登录', uin);
|
|
loginService.passwordLogin({
|
|
uin,
|
|
passwordMd5,
|
|
step: 1,
|
|
newDeviceLoginSig: new Uint8Array(),
|
|
proofWaterSig: new TextEncoder().encode(ticket),
|
|
proofWaterRand: new TextEncoder().encode(randstr),
|
|
proofWaterSid: new TextEncoder().encode(sid),
|
|
unusualDeviceCheckSig: new Uint8Array(),
|
|
}).then(res => {
|
|
if (res.result === '140022010' || res.result === '140022011') {
|
|
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
|
|
const newDevicePullQrCodeSig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
|
|
resolve({ result: false, message: '新设备需要扫码验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig });
|
|
} else if (res.result !== '0') {
|
|
const errMsg = res.loginErrorInfo?.errMsg || '验证码登录失败';
|
|
WebUiDataRuntime.setQQLoginError(errMsg);
|
|
resolve({ result: false, message: errMsg });
|
|
} else {
|
|
loginContext.isLogined = true;
|
|
WebUiDataRuntime.setQQLoginStatus(true);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
resolve({ result: true, message: '' });
|
|
}
|
|
}).catch((e) => {
|
|
logger.logError('[SnowCat] [Framework] 验证码登录异常:', e);
|
|
WebUiDataRuntime.setQQLoginError('验证码登录发生错误');
|
|
resolve({ result: false, message: '验证码登录发生错误' });
|
|
});
|
|
});
|
|
});
|
|
|
|
// 新设备验证登录(密码登录需要新设备验证时的第二步)
|
|
WebUiDataRuntime.setNewDeviceLoginCall(async (uin: string, passwordMd5: string, newDevicePullQrCodeSig: string) => {
|
|
return await new Promise((resolve) => {
|
|
if (!uin || !passwordMd5 || !newDevicePullQrCodeSig) {
|
|
return resolve({ result: false, message: '新设备验证登录失败:参数不完整' });
|
|
}
|
|
logger.log('[SnowCat] [Framework] 正在新设备验证登录', uin);
|
|
loginService.passwordLogin({
|
|
uin,
|
|
passwordMd5,
|
|
step: 2,
|
|
newDeviceLoginSig: new TextEncoder().encode(newDevicePullQrCodeSig),
|
|
proofWaterSig: new Uint8Array(),
|
|
proofWaterRand: new Uint8Array(),
|
|
proofWaterSid: new Uint8Array(),
|
|
unusualDeviceCheckSig: new Uint8Array(),
|
|
}).then(res => {
|
|
if (res.result === '140022011') {
|
|
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
|
|
const sig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
|
|
resolve({ result: false, message: '异常设备需要验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig: sig });
|
|
} else if (res.result !== '0') {
|
|
const errMsg = res.loginErrorInfo?.errMsg || '新设备验证登录失败';
|
|
WebUiDataRuntime.setQQLoginError(errMsg);
|
|
resolve({ result: false, message: errMsg });
|
|
} else {
|
|
loginContext.isLogined = true;
|
|
WebUiDataRuntime.setQQLoginStatus(true);
|
|
WebUiDataRuntime.setQQLoginError('');
|
|
resolve({ result: true, message: '' });
|
|
}
|
|
}).catch((e) => {
|
|
logger.logError('[SnowCat] [Framework] 新设备验证登录异常:', e);
|
|
WebUiDataRuntime.setQQLoginError('新设备验证登录发生错误');
|
|
resolve({ result: false, message: '新设备验证登录发生错误' });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
export class SnowCatFramework {
|
|
public core: SnowCatCore;
|
|
context: InstanceContext;
|
|
|
|
constructor (
|
|
wrapper: WrapperNodeApi,
|
|
session: NodeIQQNTWrapperSession,
|
|
logger: LogWrapper,
|
|
selfInfo: SelfInfo,
|
|
basicInfoWrapper: QQBasicInfoWrapper,
|
|
pathWrapper: SnowCatPathWrapper,
|
|
packetHandler: NativePacketHandler,
|
|
napi2nativeLoader: Napi2NativeLoader
|
|
) {
|
|
this.context = {
|
|
packetHandler,
|
|
napi2nativeLoader,
|
|
workingEnv: SnowCatCoreWorkingEnv.Framework,
|
|
wrapper,
|
|
session,
|
|
logger,
|
|
basicInfoWrapper,
|
|
pathWrapper,
|
|
};
|
|
this.core = new SnowCatCore(this.context, selfInfo);
|
|
}
|
|
}
|