package main import ( "encoding/binary" "encoding/hex" "fmt" "math/rand" "time" wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" "google.golang.org/protobuf/proto" ) // BuildTextMsgProto 构建发送文本消息的protobuf并返回hex编码的字符串 func BuildTextMsgProto(receiver, content, atUser string) (string, error) { // 构建 msgsource XML xmlStr := "" if atUser != "" { xmlStr += "" + atUser + "" } xmlStr += "1" // 生成随机消息ID msgId := rand.Int63n(1<<34) | (1 << 34) msg := &wxproto.WxSendTextMsg{ Type: 1, Body: &wxproto.WxSendTextBody{ Receiver: &wxproto.WxString{Value: receiver}, Content: []byte(content), Flag: 1, Timestamp: time.Now().Unix(), MsgId: msgId, Xml: []byte(xmlStr), }, } data, err := proto.Marshal(msg) if err != nil { return "", fmt.Errorf("marshal text proto failed: %w", err) } //fmt.Printf("[text-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0)) return hex.EncodeToString(data), nil } // BuildSendPayload 构建MMStartTask的完整payload (0x1A0字节),返回hex编码字符串 // msgType: "text", "img", "video" // 布局: [taskId(4字节)] + [payloadData(412字节)],总长固定0x1A0字节 // JS中的writePointer使用绝对偏移(0x18, 0xb8, 0x190)进行patch func BuildSendPayload(taskId int64, msgType string) string { // 公共模板 (412字节,与JS原始img/video payloadData完全一致) // 差异字节通过下方switch设置: [0],[1],[16],[28],[92],[93] payloadData := []byte{ 0x00, 0x00, 0x00, 0x00, // [0]-[3] 由msgType决定 abs 0x04 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x08 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [16] abs 0x10 0x40, 0xEC, 0x0E, 0x12, 0x01, 0x00, 0x00, 0x00, // abs 0x18 (被cgiAddr覆盖) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [28] abs 0x20 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // abs 0x28 0x00, 0x01, 0x01, 0x01, 0x00, 0xAA, 0xAA, 0xAA, // abs 0x30 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // abs 0x38 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // abs 0x40 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, // abs 0x48 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x50 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x58 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [92] abs 0x60 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x2D, // abs 0x68 "default-" 0x6C, 0x6F, 0x6E, 0x67, 0x6C, 0x69, 0x6E, 0x6B, // abs 0x70 "longlink" 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x10, // abs 0x78 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x80 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x88 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x90 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x98 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xA0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xA8 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0xB0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xB8 (被指针patch覆盖) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xC0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xC8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xD0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xD8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xE0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xE8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xF0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0xF8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x100 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x108 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x110 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x118 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x120 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x128 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x130 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x138 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x140 0x01, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x148 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x150 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x158 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x160 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x168 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x170 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x178 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x180 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // abs 0x188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x190 (被指针patch覆盖) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // abs 0x198 } // 根据msgType设置差异字节 switch msgType { case "text": payloadData[0] = 0x0A payloadData[1] = 0x02 payloadData[16] = 0x01 payloadData[28] = 0x20 payloadData[92] = 0x0A payloadData[93] = 0x02 case "img": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x22 payloadData[92] = 0x6E case "video": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x21 payloadData[92] = 0x6E case "reply": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x20 payloadData[92] = 0x6E case "file": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x20 payloadData[92] = 0x6E case "fileupload": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x27 payloadData[92] = 0x6E case "appattach": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x25 payloadData[92] = 0x6E case "voice": payloadData[0] = 0x6E payloadData[16] = 0x10 payloadData[28] = 0x21 payloadData[92] = 0x6E } // 构建完整buffer: taskId(4字节) + payloadData(412字节) = 0x1A0字节 payload := make([]byte, 0x1A0) binary.LittleEndian.PutUint32(payload[0:4], uint32(taskId)) copy(payload[4:], payloadData) return hex.EncodeToString(payload) }