diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index 7e65f930..d381c225 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -12,7 +12,12 @@ import { LogWrapper } from "@/common/log"; import { SendLongMsgResp } from "@/core/packet/proto/message/action"; import { PacketMsg } from "@/core/packet/msg/message"; import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6"; -import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element"; +import { + PacketMsgFileElement, + PacketMsgPicElement, + PacketMsgPttElement, + PacketMsgVideoElement +} from "@/core/packet/msg/element"; interface OffsetType { @@ -134,6 +139,12 @@ export class NTQQPacketApi { peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid }, e)); } + if (e instanceof PacketMsgFileElement){ + reqList.push(this.packetSession?.highwaySession.uploadFile({ + chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C, + peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid + }, e)); + } } } return Promise.all(reqList); // TODO: use promise.allSettled diff --git a/src/core/packet/highway/session.ts b/src/core/packet/highway/session.ts index cb4d5bd4..b5b2b178 100644 --- a/src/core/packet/highway/session.ts +++ b/src/core/packet/highway/session.ts @@ -8,10 +8,17 @@ import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action"; import { PacketHighwayClient } from "@/core/packet/highway/client"; import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp"; import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase"; -import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element"; -import { NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway"; +import { + PacketMsgFileElement, + PacketMsgPicElement, + PacketMsgPttElement, + PacketMsgVideoElement +} from "@/core/packet/msg/element"; +import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway"; import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils"; -import { calculateSha1StreamBytes } from "@/core/packet/utils/crypto/hash"; +import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash"; +import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6"; +import { OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800"; export const BlockSize = 1024 * 1024; @@ -22,6 +29,7 @@ interface HighwayServerAddr { export interface PacketHighwaySig { uin: string; + uid: string; sigSession: Uint8Array | null sessionKey: Uint8Array | null serverAddr: HighwayServerAddr[] @@ -40,6 +48,7 @@ export class PacketHighwaySession { this.logger = logger; this.sig = { uin: this.packetClient.napCatCore.selfInfo.uin, + uid: this.packetClient.napCatCore.selfInfo.uid, sigSession: null, sessionKey: null, serverAddr: [], @@ -117,6 +126,17 @@ export class PacketHighwaySession { } } + async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> { + await this.checkAvailable(); + if (peer.chatType === ChatType.KCHATTYPEGROUP) { + await this.uploadGroupFileReq(Number(peer.peerUid), file); + } else if (peer.chatType === ChatType.KCHATTYPEC2C) { + await this.uploadC2CFileReq(peer.peerUid, file); + } else { + throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`); + } + } + private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> { const preReq = await this.packer.packUploadGroupImgReq(groupUin, img); const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true); @@ -400,4 +420,142 @@ export class PacketHighwaySession { } ptt.msgInfo = preRespData.upload.msgInfo; } + + private async uploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<void> { + file.isGroupFile = true; + file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath); + file.fileSha1 = await calculateSha1(file.filePath); + const preReq = await this.packer.packUploadGroupFileReq(groupUin, file); + const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x6d6_0', preReq, true); + const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode( + Buffer.from(preRespRaw.hex_data, 'hex') + ); + const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(preResp.body); + if (!preRespData?.upload?.boolFileExist) { + this.logger.logDebug(`[Highway] uploadGroupFileReq file not exist, need upload!`); + const ext = new NapProtoMsg(FileUploadExt).encode({ + unknown1: 100, + unknown2: 1, + entry: { + busiBuff: { + senderUin: BigInt(this.sig.uin), + receiverUin: BigInt(groupUin), + groupCode: BigInt(groupUin), + }, + fileEntry: { + fileSize: BigInt(file.fileSize), + md5: file.fileMd5, + md5S2: file.fileMd5, + checkKey: preRespData.upload.checkKey, + fileId: preRespData.upload.fileId, + uploadKey: preRespData.upload.fileKey, + }, + clientInfo: { + clientType: 3, + appId: "100", + terminalType: 3, + clientVer: "1.1.1", + unknown: 4 + }, + fileNameInfo: { + fileName: file.fileName + }, + host: { + hosts: [ + { + url: { + host: preRespData.upload.uploadIp, + unknown: 1, + }, + port: preRespData.upload.uploadPort, + } + ] + } + }, + unknown200: 0, + }) + await this.packetHighwayClient.upload( + 71, + fs.createReadStream(file.filePath, {highWaterMark: BlockSize}), + file.fileSize, + file.fileMd5, + ext + ); + } else { + this.logger.logDebug(`[Highway] uploadGroupFileReq file exist, don't need upload!`); + } + file.fileUuid = preRespData.upload.fileId; + } + + private async uploadC2CFileReq(peerUid: string, file: PacketMsgFileElement): Promise<void> { + file.isGroupFile = false; + file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath); + file.fileSha1 = await calculateSha1(file.filePath); + const preReq = await this.packer.packUploadC2CFileReq(this.sig.uid, peerUid, file); + const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0xe37_1700', preReq, true); + const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode( + Buffer.from(preRespRaw.hex_data, 'hex') + ); + console.log("OidbSvcTrpcTcp.0xe37_1700", preRespRaw); + const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0XE37Response).decode(preResp.body); + if (!preRespData.upload?.boolFileExist) { + this.logger.logDebug(`[Highway] uploadC2CFileReq file not exist, need upload!`); + const ext = new NapProtoMsg(FileUploadExt).encode({ + unknown1: 100, + unknown2: 1, + entry: { + busiBuff: { + senderUin: BigInt(this.sig.uin), + }, + fileEntry: { + fileSize: BigInt(file.fileSize), + md5: file.fileMd5, + md5S2: file.fileMd5, + checkKey: file.fileSha1, + fileId: preRespData.upload?.uuid, + uploadKey: preRespData.upload?.mediaPlatformUploadKey, + }, + clientInfo: { + clientType: 3, + appId: "100", + terminalType: 3, + clientVer: "1.1.1", + unknown: 4 + }, + fileNameInfo: { + fileName: file.fileName + }, + host: { + hosts: [ + { + url: { + host: preRespData.upload?.uploadIp, + unknown: 1, + }, + port: preRespData.upload?.uploadPort, + } + ] + } + }, + unknown200: 1, + unknown3: 0 + }) + await this.packetHighwayClient.upload( + 95, + fs.createReadStream(file.filePath, {highWaterMark: BlockSize}), + file.fileSize, + file.fileMd5, + ext + ); + } + file.fileUuid = preRespData.upload?.uuid; + file.fileHash = preRespData.upload?.fileAddon; + const FetchExistFileReq = this.packer.packOfflineFileDownloadReq(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid); + const resp = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0xe37_800', FetchExistFileReq, true); + console.log("OidbSvcTrpcTcp.0xe37_800", resp); + const oidb_resp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(resp.hex_data, 'hex')); + file._e37_800_rsp = new NapProtoMsg(OidbSvcTrpcTcp0XE37_800Response).decode(oidb_resp.body); + file._private_send_uid = this.sig.uid; + file._private_recv_uid = peerUid; + } } diff --git a/src/core/packet/msg/builder.ts b/src/core/packet/msg/builder.ts index 97736ae2..ea6a1d3c 100644 --- a/src/core/packet/msg/builder.ts +++ b/src/core/packet/msg/builder.ts @@ -2,7 +2,8 @@ import * as crypto from "crypto"; import { PushMsgBody } from "@/core/packet/proto/message/message"; import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto"; import { LogWrapper } from "@/common/log"; -import { PacketMsg } from "@/core/packet/msg/message"; +import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message"; +import { IPacketMsgElement } from "@/core/packet/msg/element"; export class PacketMsgBuilder { private logger: LogWrapper; @@ -14,6 +15,9 @@ export class PacketMsgBuilder { buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] { return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => { const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`; + const msgContent = node.msg.reduceRight((acc: undefined | Uint8Array, msg: IPacketMsgElement<PacketSendMsgElement>) => { + return acc !== undefined ? acc : msg.buildContent(); + }, undefined); const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []); return { responseHead: { @@ -50,7 +54,8 @@ export class PacketMsgBuilder { body: { richText: { elems: msgElement - } + }, + msgContent: msgContent, } }; }); diff --git a/src/core/packet/msg/element.ts b/src/core/packet/msg/element.ts index e9693e48..e96a0c30 100644 --- a/src/core/packet/msg/element.ts +++ b/src/core/packet/msg/element.ts @@ -28,6 +28,8 @@ import { import {MsgInfo} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message"; import {ForwardMsgBuilder} from "@/common/forward-msg-builder"; +import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component"; +import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800"; // raw <-> packet // TODO: SendStructLongMsgElement @@ -39,8 +41,8 @@ export abstract class IPacketMsgElement<T extends PacketSendMsgElement> { return undefined; } - buildElement(): NapProtoEncodeStructType<typeof Elem>[] | undefined { - return undefined; + buildElement(): NapProtoEncodeStructType<typeof Elem>[] { + return []; } toPreview(): string { @@ -351,8 +353,85 @@ export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> { } export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> { + fileName: string; + filePath: string; + fileSize: number; + fileSha1?: Uint8Array; + fileMd5?: Uint8Array; + fileUuid?: string; + fileHash?: string; + isGroupFile?: boolean; + _private_send_uid?: string; + _private_recv_uid?: string; + _e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response> + constructor(element: SendFileElement) { super(element); + this.fileName = element.fileElement.fileName; + this.filePath = element.fileElement.filePath; + this.fileSize = +element.fileElement.fileSize; + } + + buildContent(): Uint8Array | undefined { + if (this.isGroupFile) return undefined; + return new NapProtoMsg(FileExtra).encode({ + file: { + fileType: 0, + fileUuid: this.fileUuid, + fileMd5: this.fileMd5, + fileName: this.fileName, + fileSize: BigInt(this.fileSize), + subcmd: 1, + dangerEvel: 0, + expireTime: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, + fileHash: this.fileHash, + }, + field6: { + field2: { + field1: this._e37_800_rsp?.body?.field30?.field110, + fileUuid: this.fileUuid, + fileName: this.fileName, + field6: this._e37_800_rsp?.body?.field30?.field3, + field7: this._e37_800_rsp?.body?.field30?.field101, + field8: this._e37_800_rsp?.body?.field30?.field100, + timestamp1: this._e37_800_rsp?.body?.field30?.timestamp1, + fileHash: this.fileHash, + selfUid: this._private_send_uid, + destUid: this._private_recv_uid, + } + } + }) + } + + buildElement(): NapProtoEncodeStructType<typeof Elem>[] { + if (!this.isGroupFile) return []; + const lb = Buffer.alloc(2); + const transElemVal = new NapProtoMsg(GroupFileExtra).encode({ + field1: 6, + fileName: this.fileName, + inner: { + info: { + busId: 102, + fileId: this.fileUuid, + fileSize: BigInt(this.fileSize), + fileName: this.fileName, + fileSha: this.fileSha1, + extInfoString: "", + fileMd5: this.fileMd5, + } + } + }) + lb.writeUInt16BE(transElemVal.length); + return [{ + transElem: { + elemType: 24, + elemValue: Buffer.concat([Buffer.from([0x01]), lb, transElemVal]) // TLV + } + }]; + } + + toPreview(): string { + return `[文件]${this.fileName}`; } } diff --git a/src/core/packet/packer.ts b/src/core/packet/packer.ts index b3f8dd74..8b7d3277 100644 --- a/src/core/packet/packer.ts +++ b/src/core/packet/packer.ts @@ -1,6 +1,6 @@ import * as zlib from "node:zlib"; import * as crypto from "node:crypto"; -import { calculateSha1 } from "@/core/packet/utils/crypto/hash"; +import { calculateSha1, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash"; import { NapProtoMsg } from "@/core/packet/proto/NapProto"; import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase"; import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202"; @@ -11,13 +11,20 @@ import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMedia import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action"; import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action"; import { PacketMsgBuilder } from "@/core/packet/msg/builder"; -import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element"; +import { + PacketMsgFileElement, + PacketMsgPicElement, + PacketMsgPttElement, + PacketMsgVideoElement +} from "@/core/packet/msg/element"; import { LogWrapper } from "@/common/log"; import { PacketMsg } from "@/core/packet/msg/message"; import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6"; import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200"; import { PacketMsgConverter } from "@/core/packet/msg/converter"; import { PacketClient } from "@/core/packet/client"; +import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700"; +import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800"; export type PacketHexStr = string & { readonly hexNya: unique symbol }; @@ -596,6 +603,65 @@ export class PacketPacker { return this.toHexStr(this.packOidbPacket(0x126D, 100, req, true, false)); } + async packUploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<PacketHexStr> { + const body = new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({ + file: { + groupUin: groupUin, + appId: 4, + busId: 102, + entrance: 6, + targetDirectory: '/', // TODO: + fileName: file.fileName, + localDirectory: `/${file.fileName}`, + fileSize: BigInt(file.fileSize), + fileMd5: file.fileMd5, + fileSha1: await calculateSha1(file.filePath), + fileSha3: Buffer.alloc(0), + field15: true + } + }); + return this.toHexStr(this.packOidbPacket(0x6D6, 0, body, true, false)); + } + + async packUploadC2CFileReq(selfUid: string, peerUid: string, file: PacketMsgFileElement): Promise<PacketHexStr> { + const body = new NapProtoMsg(OidbSvcTrpcTcp0XE37_1700).encode({ + command: 1700, + seq: 0, + upload: { + senderUid: selfUid, + receiverUid: peerUid, + fileSize: file.fileSize, + fileName: file.fileName, + md510MCheckSum: await computeMd5AndLengthWithLimit(file.filePath, 10*1024*1024), + sha1CheckSum: file.fileSha1, + localPath: "/", + md5CheckSum: file.fileMd5, + sha3CheckSum: Buffer.alloc(0) + }, + businessId: 3, + clientType: 1, + flagSupportMediaPlatform: 1 + }) + return this.toHexStr(this.packOidbPacket(0xE37, 1700, body, false, false)); + } + + packOfflineFileDownloadReq(fileUUID: string, fileHash: string, senderUid: string, receiverUid: string): PacketHexStr { + const req = this.packOidbPacket(0xE37, 800, new NapProtoMsg(OidbSvcTrpcTcp0XE37_800).encode({ + subCommand: 800, + field2: 0, + body: { + senderUid: senderUid, + receiverUid: receiverUid, + fileUuid: fileUUID, + fileHash: fileHash, + }, + field101: 3, + field102: 1, + field200: 1, + }), false, false); + return this.toHexStr(req); + } + packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr { return this.toHexStr( this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({ diff --git a/src/core/packet/proto/message/component.ts b/src/core/packet/proto/message/component.ts index 984b5f3a..c83ed1e9 100644 --- a/src/core/packet/proto/message/component.ts +++ b/src/core/packet/proto/message/component.ts @@ -113,8 +113,26 @@ export const Permission = { export const FileExtra = { file: ProtoField(1, () => NotOnlineFile), + field6: ProtoField(6, () => PrivateFileExtra), }; +export const PrivateFileExtra = { + field2: ProtoField(2, () => PrivateFileExtraField2), +} + +export const PrivateFileExtraField2 = { + field1: ProtoField(1, ScalarType.UINT32), + fileUuid: ProtoField(4, ScalarType.STRING), + fileName: ProtoField(5, ScalarType.STRING), + field6: ProtoField(6, ScalarType.UINT32), + field7: ProtoField(7, ScalarType.BYTES), + field8: ProtoField(8, ScalarType.BYTES), + timestamp1: ProtoField(9, ScalarType.UINT32), + fileHash: ProtoField(14, ScalarType.STRING), + selfUid: ProtoField(15, ScalarType.STRING), + destUid: ProtoField(16, ScalarType.STRING), +} + export const GroupFileExtra = { field1: ProtoField(1, ScalarType.UINT32), fileName: ProtoField(2, ScalarType.STRING), @@ -132,8 +150,9 @@ export const GroupFileExtraInfo = { fileSize: ProtoField(3, ScalarType.UINT64), fileName: ProtoField(4, ScalarType.STRING), field5: ProtoField(5, ScalarType.UINT32), - field7: ProtoField(7, ScalarType.STRING), - fileMd5: ProtoField(8, ScalarType.STRING), + fileSha: ProtoField(6, ScalarType.BYTES), + extInfoString: ProtoField(7, ScalarType.STRING), + fileMd5: ProtoField(8, ScalarType.BYTES), }; export const ImageExtraUrl = { diff --git a/src/core/packet/proto/oidb/Oidb.0XE37_800.ts b/src/core/packet/proto/oidb/Oidb.0XE37_800.ts new file mode 100644 index 00000000..f2e6fe5b --- /dev/null +++ b/src/core/packet/proto/oidb/Oidb.0XE37_800.ts @@ -0,0 +1,62 @@ +import { ScalarType } from "@protobuf-ts/runtime"; +import { ProtoField } from "../NapProto"; +import {OidbSvcTrpcTcp0XE37_800_1200Metadata} from "@/core/packet/proto/oidb/Oidb.0xE37_1200"; + +export const OidbSvcTrpcTcp0XE37_800 = { + subCommand: ProtoField(1, ScalarType.UINT32), + field2: ProtoField(2, ScalarType.INT32), + body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800Body, true), + field101: ProtoField(101, ScalarType.INT32), + field102: ProtoField(102, ScalarType.INT32), + field200: ProtoField(200, ScalarType.INT32) +}; + +export const OidbSvcTrpcTcp0XE37_800Body = { + senderUid: ProtoField(10, ScalarType.STRING, true), + receiverUid: ProtoField(20, ScalarType.STRING, true), + fileUuid: ProtoField(30, ScalarType.STRING, true), + fileHash: ProtoField(40, ScalarType.STRING, true) +}; + +export const OidbSvcTrpcTcp0XE37Response = { + command: ProtoField(1, ScalarType.UINT32), + seq: ProtoField(2, ScalarType.INT32), + upload: ProtoField(19, () => ApplyUploadRespV3, true), + businessId: ProtoField(101, ScalarType.INT32), + clientType: ProtoField(102, ScalarType.INT32), + flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32) +}; + +export const ApplyUploadRespV3 = { + retCode: ProtoField(10, ScalarType.INT32), + retMsg: ProtoField(20, ScalarType.STRING, true), + totalSpace: ProtoField(30, ScalarType.INT64), + usedSpace: ProtoField(40, ScalarType.INT64), + uploadedSize: ProtoField(50, ScalarType.INT64), + uploadIp: ProtoField(60, ScalarType.STRING, true), + uploadDomain: ProtoField(70, ScalarType.STRING, true), + uploadPort: ProtoField(80, ScalarType.UINT32), + uuid: ProtoField(90, ScalarType.STRING, true), + uploadKey: ProtoField(100, ScalarType.BYTES, true), + boolFileExist: ProtoField(110, ScalarType.BOOL), + packSize: ProtoField(120, ScalarType.INT32), + uploadIpList: ProtoField(130, ScalarType.STRING, false, true), // repeated + uploadHttpsPort: ProtoField(140, ScalarType.INT32), + uploadHttpsDomain: ProtoField(150, ScalarType.STRING, true), + uploadDns: ProtoField(160, ScalarType.STRING, true), + uploadLanip: ProtoField(170, ScalarType.STRING, true), + fileAddon: ProtoField(200, ScalarType.STRING, true), + mediaPlatformUploadKey: ProtoField(220, ScalarType.BYTES, true) +}; + +export const OidbSvcTrpcTcp0XE37_800Response = { + command: ProtoField(1, ScalarType.UINT32, true), + subCommand: ProtoField(2, ScalarType.UINT32, true), + body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800ResponseBody, true), + field50: ProtoField(50, ScalarType.UINT32, true), +}; + +export const OidbSvcTrpcTcp0XE37_800ResponseBody = { + field10: ProtoField(10, ScalarType.UINT32, true), + field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), +} diff --git a/src/core/packet/proto/oidb/Oidb.0xE37_1200.ts b/src/core/packet/proto/oidb/Oidb.0xE37_1200.ts index 80ecbe2c..ba2b0f16 100644 --- a/src/core/packet/proto/oidb/Oidb.0xE37_1200.ts +++ b/src/core/packet/proto/oidb/Oidb.0xE37_1200.ts @@ -30,7 +30,7 @@ export const OidbSvcTrpcTcp0XE37_1200ResponseBody = { field10: ProtoField(10, ScalarType.UINT32, true), state: ProtoField(20, ScalarType.STRING, true), result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true), - metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_1200Metadata, true), + metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), }; export const OidbSvcTrpcTcp0XE37_1200Result = { @@ -43,7 +43,7 @@ export const OidbSvcTrpcTcp0XE37_1200Result = { extra: ProtoField(120, ScalarType.BYTES, true), }; -export const OidbSvcTrpcTcp0XE37_1200Metadata = { +export const OidbSvcTrpcTcp0XE37_800_1200Metadata = { uin: ProtoField(1, ScalarType.UINT32, true), field2: ProtoField(2, ScalarType.UINT32, true), field3: ProtoField(3, ScalarType.UINT32, true), diff --git a/src/core/packet/proto/oidb/Oidb.0xE37_1700.ts b/src/core/packet/proto/oidb/Oidb.0xE37_1700.ts new file mode 100644 index 00000000..8d5c3f73 --- /dev/null +++ b/src/core/packet/proto/oidb/Oidb.0xE37_1700.ts @@ -0,0 +1,23 @@ +import { ScalarType } from "@protobuf-ts/runtime"; +import { ProtoField } from "../NapProto"; + +export const OidbSvcTrpcTcp0XE37_1700 = { + command: ProtoField(1, ScalarType.UINT32, true), + seq: ProtoField(2, ScalarType.INT32, true), + upload: ProtoField(19, () => ApplyUploadReqV3, true), + businessId: ProtoField(101, ScalarType.INT32, true), + clientType: ProtoField(102, ScalarType.INT32, true), + flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true), +} + +export const ApplyUploadReqV3 = { + senderUid: ProtoField(10, ScalarType.STRING, true), + receiverUid: ProtoField(20, ScalarType.STRING, true), + fileSize: ProtoField(30, ScalarType.UINT32, true), + fileName: ProtoField(40, ScalarType.STRING, true), + md510MCheckSum: ProtoField(50, ScalarType.BYTES, true), + sha1CheckSum: ProtoField(60, ScalarType.BYTES, true), + localPath: ProtoField(70, ScalarType.STRING, true), + md5CheckSum: ProtoField(110, ScalarType.BYTES, true), + sha3CheckSum: ProtoField(120, ScalarType.BYTES, true), +} diff --git a/src/core/packet/utils/crypto/hash.ts b/src/core/packet/utils/crypto/hash.ts index 51eaf4ec..9576e2c0 100644 --- a/src/core/packet/utils/crypto/hash.ts +++ b/src/core/packet/utils/crypto/hash.ts @@ -11,11 +11,23 @@ function sha1Stream(readable: stream.Readable) { }) as Promise<Buffer>; } +function md5Stream(readable: stream.Readable) { + return new Promise((resolve, reject) => { + readable.on('error', reject); + readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve)); + }) as Promise<Buffer>; +} + export function calculateSha1(filePath: string): Promise<Buffer> { const readable = fs.createReadStream(filePath); return sha1Stream(readable); } +export function computeMd5AndLengthWithLimit(filePath: string, limit?: number): Promise<Buffer> { + const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {}); + return md5Stream(readStream); +} + export function calculateSha1StreamBytes(filePath: string): Promise<Buffer[]> { return new Promise((resolve, reject) => { const readable = fs.createReadStream(filePath);