1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-03 18:47:41 +08:00

first commit.

This commit is contained in:
Mrs4s 2020-07-06 03:56:25 +08:00
commit ddfd670dac
62 changed files with 12625 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

416
binary/jce/reader.go Normal file
View File

@ -0,0 +1,416 @@
package jce
import (
"bytes"
"math"
"reflect"
)
type JceReader struct {
buf *bytes.Reader
data []byte
}
type HeadData struct {
Type byte
Tag int
}
func NewJceReader(data []byte) *JceReader {
buf := bytes.NewReader(data)
return &JceReader{buf: buf, data: data}
}
func (r *JceReader) readHead() (hd *HeadData, l int32) {
hd = &HeadData{}
b, _ := r.buf.ReadByte()
hd.Type = b & 0xF
hd.Tag = (int(b) & 0xF0) >> 4
if hd.Tag == 15 {
b, _ = r.buf.ReadByte()
hd.Tag = int(b) & 0xFF
return hd, 2
}
return hd, 1
}
func (r *JceReader) peakHead() (hd *HeadData, l int32) {
offset := r.buf.Size() - int64(r.buf.Len())
n := NewJceReader(r.data[offset:])
return n.readHead()
}
func (r *JceReader) skip(l int) {
r.readBytes(l)
}
func (r *JceReader) skipField(t byte) {
switch t {
case 0:
r.skip(1)
case 1:
r.skip(2)
case 2, 4:
r.skip(4)
case 3, 5:
r.skip(8)
case 6:
b, _ := r.buf.ReadByte()
r.skip(int(b))
case 7:
r.skip(int(r.readInt32()))
case 8:
s := r.ReadInt32(0)
for i := 0; i < int(s)*2; i++ {
r.skipNextField()
}
case 9:
s := r.ReadInt32(0)
for i := 0; i < int(s); i++ {
r.skipNextField()
}
case 13:
r.readHead()
s := r.ReadInt32(0)
r.skip(int(s))
case 10:
r.skipToStructEnd()
}
}
func (r *JceReader) skipNextField() {
hd, _ := r.readHead()
r.skipField(hd.Type)
}
func (r *JceReader) SkipField(c int) {
for i := 0; i < c; i++ {
r.skipNextField()
}
}
func (r *JceReader) readBytes(len int) []byte {
b := make([]byte, len)
_, err := r.buf.Read(b)
if err != nil {
panic(err)
}
return b
}
func (r *JceReader) readByte() byte {
return r.readBytes(1)[0]
}
func (r *JceReader) readUInt16() uint16 {
f, _ := r.buf.ReadByte()
s, err := r.buf.ReadByte()
if err != nil {
panic(err)
}
return uint16((int32(f) << 8) + int32(s))
}
func (r *JceReader) readInt32() int32 {
b := r.readBytes(4)
return (int32(b[0]) << 24) | (int32(b[1]) << 16) | (int32(b[2]) << 8) | int32(b[3])
}
func (r *JceReader) readInt64() int64 {
b := r.readBytes(8)
return (int64(b[0]) << 56) | (int64(b[1]) << 48) | (int64(b[2]) << 40) | (int64(b[3]) << 32) | (int64(b[4]) << 24) | (int64(b[5]) << 16) | (int64(b[6]) << 8) | int64(b[7])
}
func (r *JceReader) readFloat32() float32 {
b := r.readInt32()
return math.Float32frombits(uint32(b))
}
func (r *JceReader) readFloat64() float64 {
b := r.readInt64()
return math.Float64frombits(uint64(b))
}
func (r *JceReader) skipToTag(tag int) bool {
for {
hd, l := r.peakHead()
if tag <= hd.Tag || hd.Type == 11 {
return tag == hd.Tag
}
r.skip(int(l))
r.skipField(hd.Type)
}
}
func (r *JceReader) skipToStructEnd() {
for {
hd, _ := r.readHead()
r.skipField(hd.Type)
if hd.Type == 11 {
return
}
}
}
func (r *JceReader) ReadByte(tag int) byte {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 0:
return r.readByte()
default:
return 0
}
}
func (r *JceReader) ReadBool(tag int) bool {
return r.ReadByte(tag) != 0
}
func (r *JceReader) ReadInt16(tag int) int16 {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 0:
return int16(r.readByte())
case 1:
return int16(r.readUInt16())
default:
return 0
}
}
func (r *JceReader) ReadInt32(tag int) int32 {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 0:
return int32(r.readByte())
case 1:
return int32(r.readUInt16())
case 2:
return r.readInt32()
default:
return 0
}
}
func (r *JceReader) ReadInt64(tag int) int64 {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 0:
return int64(r.readByte())
case 1:
return int64(int16(r.readUInt16()))
case 2:
return int64(r.readInt32())
case 3:
return r.readInt64()
default:
return 0
}
}
func (r *JceReader) ReadFloat32(tag int) float32 {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 4:
return r.readFloat32()
default:
return 0
}
}
func (r *JceReader) ReadFloat64(tag int) float64 {
if !r.skipToTag(tag) {
return 0
}
hd, _ := r.readHead()
switch hd.Type {
case 12:
return 0
case 4:
return float64(r.readFloat32())
case 5:
return r.readFloat64()
default:
return 0
}
}
func (r *JceReader) ReadString(tag int) string {
if !r.skipToTag(tag) {
return ""
}
hd, _ := r.readHead()
switch hd.Type {
case 6:
return string(r.readBytes(int(r.readByte())))
case 7:
return string(r.readBytes(int(r.readInt32())))
default:
return ""
}
}
// ReadAny Read any type via tag, unsupported JceStruct
func (r *JceReader) ReadAny(tag int) interface{} {
if !r.skipToTag(tag) {
return nil
}
hd, _ := r.readHead()
switch hd.Type {
case 0:
return r.readByte()
case 1:
return r.readUInt16()
case 2:
return r.readInt32()
case 3:
return r.readInt64()
case 4:
return r.readFloat32()
case 5:
return r.readFloat64()
case 6:
return string(r.readBytes(int(r.readByte())))
case 7:
return string(r.readBytes(int(r.readInt32())))
case 8:
s := r.ReadInt32(0)
m := make(map[interface{}]interface{})
for i := 0; i < int(s); i++ {
m[r.ReadAny(0)] = r.ReadAny(1)
}
return m
case 9:
var sl []interface{}
s := r.ReadInt32(0)
for i := 0; i < int(s); i++ {
sl = append(sl, r.ReadAny(0))
}
return sl
case 12:
return 0
case 13:
r.readHead()
return r.readBytes(int(r.ReadInt32(0)))
default:
return nil
}
}
func (r *JceReader) ReadMapF(tag int, f func(interface{}, interface{})) {
if !r.skipToTag(tag) {
return
}
r.readHead()
s := r.ReadInt32(0)
for i := 0; i < int(s); i++ {
k := r.ReadAny(0)
v := r.ReadAny(1)
if k != nil {
f(k, v)
}
}
}
func (r *JceReader) readObject(t reflect.Type, tag int) reflect.Value {
switch t.Kind() {
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
var i int64
r.ReadObject(&i, tag)
return reflect.ValueOf(i)
case reflect.String:
var s string
r.ReadObject(&s, tag)
return reflect.ValueOf(s)
case reflect.Slice:
s := reflect.New(t.Elem()).Interface().(IJecStruct)
r.readHead()
s.ReadFrom(r)
r.skipToStructEnd()
return reflect.ValueOf(s).Elem()
}
return reflect.ValueOf(nil)
}
func (r *JceReader) ReadSlice(i interface{}, tag int) {
t := reflect.TypeOf(i)
v := reflect.ValueOf(i).Elem()
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Slice {
return
}
if v.IsNil() {
return
}
if !r.skipToTag(tag) {
return
}
hd, _ := r.readHead()
if hd.Type == 9 {
s := r.ReadInt32(0)
for i := 0; i < int(s); i++ {
val := r.readObject(t.Elem(), 0)
v.Set(reflect.Append(v, val))
}
}
if hd.Type == 13 {
r.readHead()
arr := r.readBytes(int(r.ReadInt32(0)))
for _, b := range arr {
v.Set(reflect.Append(v, reflect.ValueOf(b)))
}
}
}
func (r *JceReader) ReadObject(i interface{}, tag int) {
va := reflect.ValueOf(i)
if va.Kind() != reflect.Ptr || va.IsNil() {
return
}
switch o := i.(type) {
case *byte:
*o = r.ReadByte(tag)
case *bool:
*o = r.ReadBool(tag)
case *int16:
*o = r.ReadInt16(tag)
case *int:
*o = int(r.ReadInt32(tag))
case *int32:
*o = r.ReadInt32(tag)
case *int64:
*o = r.ReadInt64(tag)
case *float32:
*o = r.ReadFloat32(tag)
case *float64:
*o = r.ReadFloat64(tag)
case *string:
*o = r.ReadString(tag)
case IJecStruct:
o.ReadFrom(r)
}
}

397
binary/jce/structs.go Normal file
View File

@ -0,0 +1,397 @@
package jce
type IJecStruct interface {
ToBytes() []byte
ReadFrom(*JceReader)
}
// TODO: code gen
type (
RequestPacket struct {
IVersion int16 `jceId:"1"`
CPacketType byte `jceId:"2"`
IMessageType int32 `jceId:"3"`
IRequestId int32 `jceId:"4"`
SServantName string `jceId:"5"`
SFuncName string `jceId:"6"`
SBuffer []byte `jceId:"7"`
ITimeout int32 `jceId:"8"`
Context map[string]string `jceId:"9"`
Status map[string]string `jceId:"10"`
}
RequestDataVersion3 struct {
Map map[string][]byte `jceId:"0"`
}
RequestDataVersion2 struct {
Map map[string]map[string][]byte `jceId:"0"`
}
SvcReqRegister struct {
Uin int64 `jceId:"0"`
Bid int64 `jceId:"1"`
ConnType byte `jceId:"2"`
Other string `jceId:"3"`
Status int32 `jceId:"4"`
OnlinePush byte `jceId:"5"`
IsOnline byte `jceId:"6"`
IsShowOnline byte `jceId:"7"`
KickPC byte `jceId:"8"`
KickWeak byte `jceId:"9"`
Timestamp int64 `jceId:"10"`
IOSVersion int64 `jceId:"11"`
NetType byte `jceId:"12"`
BuildVer string `jceId:"13"`
RegType byte `jceId:"14"`
DevParam []byte `jceId:"15"`
Guid []byte `jceId:"16"`
LocaleId int32 `jceId:"17"`
SilentPush byte `jceId:"18"`
DevName string `jceId:"19"`
DevType string `jceId:"20"`
OSVer string `jceId:"21"`
OpenPush byte `jceId:"22"`
LargeSeq int64 `jceId:"23"`
LastWatchStartTime int64 `jceId:"24"`
OldSSOIp int64 `jceId:"26"`
NewSSOIp int64 `jceId:"27"`
ChannelNo string `jceId:"28"`
CPID int64 `jceId:"29"`
VendorName string `jceId:"30"`
VendorOSName string `jceId:"31"`
IOSIdfa string `jceId:"32"`
B769 []byte `jceId:"33"`
IsSetStatus byte `jceId:"34"`
ServerBuf []byte `jceId:"35"`
SetMute byte `jceId:"36"`
}
FriendListRequest struct {
Reqtype int32 `jceId:"0"`
IfReflush byte `jceId:"1"`
Uin int64 `jceId:"2"`
StartIndex int16 `jceId:"3"`
FriendCount int16 `jceId:"4"`
GroupId byte `jceId:"5"`
IfGetGroupInfo byte `jceId:"6"`
GroupStartIndex byte `jceId:"7"`
GroupCount byte `jceId:"8"`
IfGetMSFGroup byte `jceId:"9"`
IfShowTermType byte `jceId:"10"`
Version int64 `jceId:"11"`
UinList []int64 `jceId:"12"`
AppType int32 `jceId:"13"`
IfGetDOVId byte `jceId:"14"`
IfGetBothFlag byte `jceId:"15"`
D50 []byte `jceId:"16"`
D6B []byte `jceId:"17"`
SnsTypeList []int64 `jceId:"18"`
}
FriendInfo struct {
FriendUin int64 `jceId:"0"`
GroupId byte `jceId:"1"`
FaceId int16 `jceId:"2"`
Remark string `jceId:"3"`
QQType byte `jceId:"4"`
Status byte `jceId:"5"`
MemberLevel byte `jceId:"6"`
IsMqqOnLine byte `jceId:"7"`
QQOnlineState byte `jceId:"8"`
IsIphoneOnline byte `jceId:"9"`
DetailStatusFlag byte `jceId:"10"`
QQOnlineStateV2 byte `jceId:"11"`
ShowName string `jceId:"12"`
IsRemark byte `jceId:"13"`
Nick string `jceId:"14"`
SpecialFlag byte `jceId:"15"`
IMGroupID []byte `jceId:"16"`
MSFGroupID []byte `jceId:"17"`
TermType int32 `jceId:"18"`
Network byte `jceId:"20"`
Ring []byte `jceId:"21"`
AbiFlag int64 `jceId:"22"`
FaceAddonId int64 `jceId:"23"`
NetworkType int32 `jceId:"24"`
VipFont int64 `jceId:"25"`
IconType int32 `jceId:"26"`
TermDesc string `jceId:"27"`
ColorRing int64 `jceId:"28"`
ApolloFlag byte `jceId:"29"`
ApolloTimestamp int64 `jceId:"30"`
Sex byte `jceId:"31"`
FounderFont int64 `jceId:"32"`
EimId string `jceId:"33"`
EimMobile string `jceId:"34"`
OlympicTorch byte `jceId:"35"`
ApolloSignTime int64 `jceId:"36"`
LaviUin int64 `jceId:"37"`
TagUpdateTime int64 `jceId:"38"`
GameLastLoginTime int64 `jceId:"39"`
GameAppId int64 `jceId:"40"`
CardID []byte `jceId:"41"`
BitSet int64 `jceId:"42"`
KingOfGloryFlag byte `jceId:"43"`
KingOfGloryRank int64 `jceId:"44"`
MasterUin string `jceId:"45"`
LastMedalUpdateTime int64 `jceId:"46"`
FaceStoreId int64 `jceId:"47"`
FontEffect int64 `jceId:"48"`
DOVId string `jceId:"49"`
BothFlag int64 `jceId:"50"`
CentiShow3DFlag byte `jceId:"51"`
IntimateInfo []byte `jceId:"52"`
ShowNameplate byte `jceId:"53"`
NewLoverDiamondFlag byte `jceId:"54"`
ExtSnsFrdData []byte `jceId:"55"`
MutualMarkData []byte `jceId:"56"`
}
TroopListRequest struct {
Uin int64 `jceId:"0"`
GetMSFMsgFlag byte `jceId:"1"`
Cookies []byte `jceId:"2"`
GroupInfo []int64 `jceId:"3"`
GroupFlagExt byte `jceId:"4"`
Version int32 `jceId:"5"`
CompanyId int64 `jceId:"6"`
VersionNum int64 `jceId:"7"`
GetLongGroupName byte `jceId:"8"`
}
TroopNumber struct {
GroupUin int64 `jceId:"0"`
GroupCode int64 `jceId:"1"`
Flag byte `jceId:"2"`
GroupInfoSeq int64 `jceId:"3"`
GroupName string `jceId:"4"`
GroupMemo string `jceId:"5"`
GroupFlagExt int64 `jceId:"6"`
GroupRankSeq int64 `jceId:"7"`
CertificationType int64 `jceId:"8"`
ShutUpTimestamp int64 `jceId:"9"`
MyShutUpTimestamp int64 `jceId:"10"`
CmdUinUinFlag int64 `jceId:"11"`
AdditionalFlag int64 `jceId:"12"`
GroupTypeFlag int64 `jceId:"13"`
GroupSecType int64 `jceId:"14"`
GroupSecTypeInfo int64 `jceId:"15"`
GroupClassExt int64 `jceId:"16"`
AppPrivilegeFlag int64 `jceId:"17"`
SubscriptionUin int64 `jceId:"18"`
MemberNum int64 `jceId:"19"`
MemberNumSeq int64 `jceId:"20"`
MemberCardSeq int64 `jceId:"21"`
GroupFlagExt3 int64 `jceId:"22"`
GroupOwnerUin int64 `jceId:"23"`
IsConfGroup byte `jceId:"24"`
IsModifyConfGroupFace byte `jceId:"25"`
IsModifyConfGroupName byte `jceId:"26"`
CmdUinJoinTime int64 `jceId:"27"`
CompanyId int64 `jceId:"28"`
MaxGroupMemberNum int64 `jceId:"29"`
CmdUinGroupMask int64 `jceId:"30"`
GuildAppId int64 `jceId:"31"`
GuildSubType int64 `jceId:"32"`
CmdUinRingtoneID int64 `jceId:"33"`
CmdUinFlagEx2 int64 `jceId:"34"`
}
TroopMemberListRequest struct {
Uin int64 `jceId:"0"`
GroupCode int64 `jceId:"1"`
NextUin int64 `jceId:"2"`
GroupUin int64 `jceId:"3"`
Version int64 `jceId:"4"`
ReqType int64 `jceId:"5"`
GetListAppointTime int64 `jceId:"6"`
RichCardNameVer byte `jceId:"7"`
}
TroopMemberInfo struct {
MemberUin int64 `jceId:"0"`
FaceId int16 `jceId:"1"`
Age byte `jceId:"2"`
Gender byte `jceId:"3"`
Nick string `jceId:"4"`
Status byte `jceId:"5"`
ShowName string `jceId:"6"`
Name string `jceId:"8"`
Memo string `jceId:"12"`
AutoRemark string `jceId:"13"`
MemberLevel int64 `jceId:"14"`
JoinTime int64 `jceId:"15"`
LastSpeakTime int64 `jceId:"16"`
CreditLevel int64 `jceId:"17"`
Flag int64 `jceId:"18"`
FlagExt int64 `jceId:"19"`
Point int64 `jceId:"20"`
Concerned byte `jceId:"21"`
Shielded byte `jceId:"22"`
SpecialTitle string `jceId:"23"`
SpecialTitleExpireTime int64 `jceId:"24"`
Job string `jceId:"25"`
ApolloFlag byte `jceId:"26"`
ApolloTimestamp int64 `jceId:"27"`
GlobalGroupLevel int64 `jceId:"28"`
TitleId int64 `jceId:"29"`
ShutUpTimestap int64 `jceId:"30"`
GlobalGroupPoint int64 `jceId:"31"`
RichCardNameVer byte `jceId:"33"`
VipType int64 `jceId:"34"`
VipLevel int64 `jceId:"35"`
BigClubLevel int64 `jceId:"36"`
BigClubFlag int64 `jceId:"37"`
Nameplate int64 `jceId:"38"`
GroupHonor []byte `jceId:"39"`
}
)
func (pkt *RequestPacket) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *RequestPacket) ReadFrom(r *JceReader) {
pkt.SBuffer = []byte{}
pkt.Context = make(map[string]string)
pkt.Status = make(map[string]string)
pkt.IVersion = r.ReadInt16(1)
pkt.CPacketType = r.ReadByte(2)
pkt.IMessageType = r.ReadInt32(3)
pkt.IRequestId = r.ReadInt32(4)
pkt.SServantName = r.ReadString(5)
pkt.SFuncName = r.ReadString(6)
r.ReadSlice(&pkt.SBuffer, 7)
pkt.ITimeout = r.ReadInt32(8)
r.ReadMapF(9, func(k interface{}, v interface{}) { pkt.Context[k.(string)] = v.(string) })
r.ReadMapF(10, func(k interface{}, v interface{}) { pkt.Status[k.(string)] = v.(string) })
}
func (pkt *RequestDataVersion3) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *RequestDataVersion3) ReadFrom(r *JceReader) {
pkt.Map = make(map[string][]byte)
r.ReadMapF(0, func(k interface{}, v interface{}) {
pkt.Map[k.(string)] = v.([]byte)
})
}
func (pkt *RequestDataVersion2) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *RequestDataVersion2) ReadFrom(r *JceReader) {
pkt.Map = make(map[string]map[string][]byte)
r.ReadMapF(0, func(k interface{}, v interface{}) {
pkt.Map[k.(string)] = make(map[string][]byte)
for k2, v := range v.(map[interface{}]interface{}) {
pkt.Map[k.(string)][k2.(string)] = v.([]byte)
}
})
}
func (pkt *SvcReqRegister) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *SvcReqRegister) ReadFrom(r *JceReader) {
}
func (pkt *FriendListRequest) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *FriendListRequest) ReadFrom(r *JceReader) {
}
func (pkt *FriendInfo) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *FriendInfo) ReadFrom(r *JceReader) {
pkt.FriendUin = r.ReadInt64(0)
pkt.GroupId = r.ReadByte(1)
pkt.FaceId = r.ReadInt16(2)
pkt.Remark = r.ReadString(3)
pkt.Status = r.ReadByte(5)
pkt.MemberLevel = r.ReadByte(6)
pkt.Nick = r.ReadString(14)
pkt.Network = r.ReadByte(20)
pkt.NetworkType = r.ReadInt32(24)
pkt.CardID = []byte{}
r.ReadObject(&pkt.CardID, 41)
}
func (pkt *TroopListRequest) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *TroopListRequest) ReadFrom(r *JceReader) {
}
func (pkt *TroopNumber) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *TroopNumber) ReadFrom(r *JceReader) {
pkt.GroupUin = r.ReadInt64(0)
pkt.GroupCode = r.ReadInt64(1)
pkt.GroupName = r.ReadString(4)
pkt.GroupMemo = r.ReadString(5)
pkt.MemberNum = r.ReadInt64(19)
pkt.GroupOwnerUin = r.ReadInt64(23)
pkt.MaxGroupMemberNum = r.ReadInt64(29)
}
func (pkt *TroopMemberListRequest) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *TroopMemberListRequest) ReadFrom(r *JceReader) {
}
func (pkt *TroopMemberInfo) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}
func (pkt *TroopMemberInfo) ReadFrom(r *JceReader) {
pkt.MemberUin = r.ReadInt64(0)
pkt.FaceId = r.ReadInt16(1)
pkt.Nick = r.ReadString(4)
pkt.ShowName = r.ReadString(6)
pkt.Name = r.ReadString(8)
pkt.AutoRemark = r.ReadString(13)
pkt.MemberLevel = r.ReadInt64(14)
pkt.JoinTime = r.ReadInt64(15)
pkt.LastSpeakTime = r.ReadInt64(16)
pkt.SpecialTitle = r.ReadString(23)
pkt.SpecialTitleExpireTime = r.ReadInt64(24)
pkt.Job = r.ReadString(25)
}

192
binary/jce/writer.go Normal file
View File

@ -0,0 +1,192 @@
package jce
import (
"bytes"
goBinary "encoding/binary"
"reflect"
"strconv"
)
type JceWriter struct {
buf *bytes.Buffer
}
func NewJceWriter() *JceWriter {
return &JceWriter{buf: new(bytes.Buffer)}
}
func (w *JceWriter) writeHead(t byte, tag int) {
if tag < 15 {
b := byte(tag<<4) | t
w.buf.WriteByte(b)
} else if tag < 256 {
b := 0xF0 | t
w.buf.WriteByte(b)
w.buf.WriteByte(byte(tag))
}
}
func (w *JceWriter) WriteByte(b byte, tag int) {
if b == 0 {
w.writeHead(12, tag)
} else {
w.writeHead(0, tag)
w.buf.WriteByte(b)
}
}
func (w *JceWriter) WriteBool(b bool, tag int) {
var by byte = 0
if b {
by = 1
}
w.WriteByte(by, tag)
}
func (w *JceWriter) WriteInt16(n int16, tag int) {
if n >= -128 && n <= 127 {
w.WriteByte(byte(n), tag)
return
}
w.writeHead(1, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, n)
}
func (w *JceWriter) WriteInt32(n int32, tag int) {
if n >= -32768 && n <= 32767 { // ? if ((n >= 32768) && (n <= 32767))
w.WriteInt16(int16(n), tag)
return
}
w.writeHead(2, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, n)
}
func (w *JceWriter) WriteInt64(n int64, tag int) {
if n >= -2147483648 && n <= 2147483647 {
w.WriteInt32(int32(n), tag)
return
}
w.writeHead(3, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, n)
}
func (w *JceWriter) WriteFloat32(n float32, tag int) {
w.writeHead(4, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, n)
}
func (w *JceWriter) WriteFloat64(n float64, tag int) {
w.writeHead(5, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, n)
}
func (w *JceWriter) WriteString(s string, tag int) {
by := []byte(s)
if len(by) > 255 {
w.writeHead(7, tag)
_ = goBinary.Write(w.buf, goBinary.BigEndian, len(by))
w.buf.Write(by)
return
}
w.writeHead(6, tag)
w.buf.WriteByte(byte(len(by)))
w.buf.Write(by)
}
func (w *JceWriter) WriteBytes(l []byte, tag int) {
w.writeHead(13, tag)
w.writeHead(0, 0)
w.WriteInt32(int32(len(l)), 0)
w.buf.Write(l)
}
func (w *JceWriter) WriteInt64Slice(l []int64, tag int) {
w.writeHead(9, tag)
if len(l) == 0 {
w.WriteInt32(0, 0)
return
}
w.WriteInt32(int32(len(l)), 0)
for _, v := range l {
w.WriteInt64(v, 0)
}
}
func (w *JceWriter) WriteMap(m interface{}, tag int) {
if m == nil {
w.writeHead(8, tag)
w.WriteInt32(0, 0)
return
}
va := reflect.ValueOf(m)
if va.Kind() != reflect.Map {
return
}
w.writeHead(8, tag)
w.WriteInt32(int32(len(va.MapKeys())), 0)
for _, k := range va.MapKeys() {
v := va.MapIndex(k)
w.WriteObject(k.Interface(), 0)
w.WriteObject(v.Interface(), 1)
}
}
func (w *JceWriter) WriteObject(i interface{}, tag int) {
t := reflect.TypeOf(i)
if t.Kind() == reflect.Map {
w.WriteMap(i, tag)
return
}
switch o := i.(type) {
case byte:
w.WriteByte(o, tag)
case bool:
w.WriteBool(o, tag)
case int16:
w.WriteInt16(o, tag)
case int32:
w.WriteInt32(o, tag)
case int64:
w.WriteInt64(o, tag)
case float32:
w.WriteFloat32(o, tag)
case float64:
w.WriteFloat64(o, tag)
case string:
w.WriteString(o, tag)
case []byte:
w.WriteBytes(o, tag)
case []int64:
w.WriteInt64Slice(o, tag)
case IJecStruct:
w.WriteJceStruct(o, tag)
}
}
func (w *JceWriter) WriteJceStructRaw(s IJecStruct) {
var (
t = reflect.TypeOf(s).Elem()
v = reflect.ValueOf(s).Elem()
)
for i := 0; i < t.NumField(); i++ {
strId := t.Field(i).Tag.Get("jceId")
if strId == "" {
continue
}
id, err := strconv.Atoi(strId)
if err != nil {
continue
}
w.WriteObject(v.Field(i).Interface(), id)
}
}
func (w *JceWriter) WriteJceStruct(s IJecStruct, tag int) {
w.writeHead(10, tag)
w.WriteJceStructRaw(s)
w.writeHead(11, 0)
}
func (w *JceWriter) Bytes() []byte {
return w.buf.Bytes()
}

146
binary/reader.go Normal file
View File

@ -0,0 +1,146 @@
package binary
import (
"bytes"
"net"
)
type Reader struct {
buf *bytes.Reader
}
type NetworkReader struct {
conn net.Conn
}
type TlvMap map[uint16][]byte
// --- ByteStream reader ---
func NewReader(data []byte) *Reader {
buf := bytes.NewReader(data)
return &Reader{
buf: buf,
}
}
func (r *Reader) ReadByte() byte {
b, err := r.buf.ReadByte()
if err != nil {
panic(err)
}
return b
}
func (r *Reader) ReadBytes(len int) []byte {
b := make([]byte, len)
_, err := r.buf.Read(b)
if err != nil {
panic(err)
}
return b
}
func (r *Reader) ReadBytesShort() []byte {
return r.ReadBytes(int(r.ReadUInt16()))
}
func (r *Reader) ReadUInt16() uint16 {
f, _ := r.buf.ReadByte()
s, err := r.buf.ReadByte()
if err != nil {
panic(err)
}
return uint16((int32(f) << 8) + int32(s))
}
func (r *Reader) ReadInt32() int32 {
b := r.ReadBytes(4)
return (int32(b[0]) << 24) | (int32(b[1]) << 16) | (int32(b[2]) << 8) | int32(b[3])
}
func (r *Reader) ReadString() string {
data := r.ReadBytes(int(r.ReadInt32() - 4))
return string(data)
}
func (r *Reader) ReadStringShort() string {
data := r.ReadBytes(int(r.ReadUInt16()))
return string(data)
}
func (r *Reader) ReadStringLimit(limit int) string {
data := r.ReadBytes(limit)
return string(data)
}
func (r *Reader) ReadAvailable() []byte {
return r.ReadBytes(r.buf.Len())
}
func (r *Reader) ReadTlvMap(tagSize int) (m TlvMap) {
defer func() {
if r := recover(); r != nil {
// TODO: error
}
}()
m = make(map[uint16][]byte)
for {
if r.Len() < tagSize {
return m
}
var k uint16
if tagSize == 1 {
k = uint16(r.ReadByte())
} else if tagSize == 2 {
k = r.ReadUInt16()
} else if tagSize == 4 {
k = uint16(r.ReadInt32())
}
if k == 255 {
return m
}
m[k] = r.ReadBytes(int(r.ReadUInt16()))
}
}
func (r *Reader) Len() int {
return r.buf.Len()
}
func (tlv TlvMap) Exists(key uint16) bool {
if _, ok := tlv[key]; ok {
return true
}
return false
}
// --- Network reader ---
func NewNetworkReader(conn net.Conn) *NetworkReader {
return &NetworkReader{conn: conn}
}
func (r *NetworkReader) ReadByte() byte {
buf := make([]byte, 1)
n, err := r.conn.Read(buf)
if err != nil {
panic(err)
}
if n != 1 {
return r.ReadByte()
}
return buf[0]
}
func (r *NetworkReader) ReadBytes(len int) []byte {
buf := make([]byte, len)
for i := 0; i < len; i++ {
buf[i] = r.ReadByte()
}
return buf
}
func (r *NetworkReader) ReadInt32() int32 {
return (int32(r.ReadByte()) << 24) | (int32(r.ReadByte()) << 16) | (int32(r.ReadByte()) << 8) | int32(r.ReadByte())
}

143
binary/tea.go Normal file
View File

@ -0,0 +1,143 @@
package binary
import (
"bytes"
"encoding/binary"
"math/rand"
"time"
)
const (
delta = uint32(0x9E3779B9)
fillnor = 0xF8
)
type teaCipher struct {
keys [4]uint32
value []byte
byte8 [8]byte
ubyte32 [2]uint32
xor [8]byte //xor
fxor [8]byte //first xor
lxor [8]byte //last xor
nxor [8]byte //new xor Decrypt add
balebuff *bytes.Buffer
seedrand *rand.Rand
}
func NewTeaCipher(key []byte) *teaCipher {
if len(key) != 16 {
return nil
}
cipher := &teaCipher{
balebuff: bytes.NewBuffer(nil),
}
for i := 0; i < 4; i++ {
cipher.keys[i] = binary.BigEndian.Uint32(key[i*4:])
}
cipher.seedrand = rand.New(rand.NewSource(time.Now().UnixNano()))
return cipher
}
func (c *teaCipher) Encrypt(value []byte) []byte {
c.balebuff.Reset()
vl := len(value)
filln := (8 - (vl + 2)) % 8
if filln < 0 {
filln += 2 + 8
} else {
filln += 2
}
bindex := filln + 1
if bindex <= 0 {
return nil
}
rands := make([]byte, bindex)
for i := 1; i < bindex; i++ {
rands[i] = byte((c.seedrand.Intn(236) + 1))
}
rands[0] = byte((filln - 2) | fillnor)
c.balebuff.Write(rands)
c.balebuff.Write(value)
c.balebuff.Write([]byte{00, 00, 00, 00, 00, 00, 00})
vl = c.balebuff.Len()
c.value = c.balebuff.Bytes()
c.balebuff.Reset()
for i := 0; i < vl; i += 8 {
c.xor = xor(c.value[i:i+8], c.fxor[0:8])
c.ubyte32[0] = binary.BigEndian.Uint32(c.xor[0:4])
c.ubyte32[1] = binary.BigEndian.Uint32(c.xor[4:8])
c.encipher()
c.fxor = xor(c.byte8[0:8], c.lxor[0:8])
c.balebuff.Write(c.fxor[0:8])
c.lxor = c.xor
}
return c.balebuff.Bytes()
}
func (c *teaCipher) Decrypt(value []byte) []byte {
vl := len(value)
if vl <= 0 || (vl%8) != 0 {
return nil
}
c.balebuff.Reset()
c.ubyte32[0] = binary.BigEndian.Uint32(value[0:4])
c.ubyte32[1] = binary.BigEndian.Uint32(value[4:8])
c.decipher()
copy(c.lxor[0:8], value[0:8])
c.fxor = c.byte8
pos := int((c.byte8[0] & 0x7) + 2)
c.balebuff.Write(c.byte8[0:8])
for i := 8; i < vl; i += 8 {
c.xor = xor(value[i:i+8], c.fxor[0:8])
c.ubyte32[0] = binary.BigEndian.Uint32(c.xor[0:4])
c.ubyte32[1] = binary.BigEndian.Uint32(c.xor[4:8])
c.decipher()
c.nxor = xor(c.byte8[0:8], c.lxor[0:8])
c.balebuff.Write(c.nxor[0:8])
c.fxor = xor(c.nxor[0:8], c.lxor[0:8])
copy(c.lxor[0:8], value[i:i+8])
}
pos++
c.value = c.balebuff.Bytes()
nl := c.balebuff.Len()
if pos >= c.balebuff.Len() || (nl-7) <= pos {
return nil
}
return c.value[pos : nl-7]
}
func (c *teaCipher) encipher() {
sum := delta
for i := 0x10; i > 0; i-- {
c.ubyte32[0] += ((c.ubyte32[1] << 4 & 0xFFFFFFF0) + c.keys[0]) ^ (c.ubyte32[1] + sum) ^ ((c.ubyte32[1] >> 5 & 0x07ffffff) + c.keys[1])
c.ubyte32[1] += ((c.ubyte32[0] << 4 & 0xFFFFFFF0) + c.keys[2]) ^ (c.ubyte32[0] + sum) ^ ((c.ubyte32[0] >> 5 & 0x07ffffff) + c.keys[3])
sum += delta
}
binary.BigEndian.PutUint32(c.byte8[0:4], c.ubyte32[0])
binary.BigEndian.PutUint32(c.byte8[4:8], c.ubyte32[1])
}
func (c *teaCipher) decipher() {
sum := delta
sum = (sum << 4) & 0xffffffff
for i := 0x10; i > 0; i-- {
c.ubyte32[1] -= (((c.ubyte32[0] << 4 & 0xFFFFFFF0) + c.keys[2]) ^ (c.ubyte32[0] + sum) ^ ((c.ubyte32[0] >> 5 & 0x07ffffff) + c.keys[3]))
c.ubyte32[1] &= 0xffffffff
c.ubyte32[0] -= (((c.ubyte32[1] << 4 & 0xFFFFFFF0) + c.keys[0]) ^ (c.ubyte32[1] + sum) ^ ((c.ubyte32[1] >> 5 & 0x07ffffff) + c.keys[1]))
c.ubyte32[0] &= 0xffffffff
sum -= delta
}
binary.BigEndian.PutUint32(c.byte8[0:4], c.ubyte32[0])
binary.BigEndian.PutUint32(c.byte8[4:8], c.ubyte32[1])
}
func xor(a, b []byte) (bts [8]byte) {
l := len(a)
for i := 0; i < l; i += 4 {
binary.BigEndian.PutUint32(bts[i:i+4], binary.BigEndian.Uint32(a[i:i+4])^binary.BigEndian.Uint32(b[i:i+4]))
}
return bts
}

77
binary/utils.go Normal file
View File

@ -0,0 +1,77 @@
package binary
import (
"bytes"
"compress/zlib"
"crypto/rand"
binary2 "encoding/binary"
"encoding/hex"
"fmt"
"io"
"math/big"
"strings"
)
func ZlibUncompress(src []byte) []byte {
b := bytes.NewReader(src)
var out bytes.Buffer
r, _ := zlib.NewReader(b)
io.Copy(&out, r)
return out.Bytes()
}
func RandomString(len int) string {
var res string
var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
b := bytes.NewBufferString(str)
length := b.Len()
bigInt := big.NewInt(int64(length))
for i := 0; i < len; i++ {
randomInt, _ := rand.Int(rand.Reader, bigInt)
res += string(str[randomInt.Int64()])
}
return res
}
func CalculateImageResourceId(md5 []byte) string {
return strings.ToUpper(fmt.Sprintf(
"{%s-%s-%s-%s-%s}.png",
hex.EncodeToString(md5[0:4]), hex.EncodeToString(md5[4:6]), hex.EncodeToString(md5[6:8]),
hex.EncodeToString(md5[8:10]), hex.EncodeToString(md5[10:]),
))
}
func ToIPV4Address(arr []byte) string {
if len(arr) != 4 {
return ""
}
return fmt.Sprintf("%d.%d.%d.%d", arr[0], arr[1], arr[2], arr[3])
}
func UInt32ToIPV4Address(i uint32) string {
addr := make([]byte, 4)
binary2.LittleEndian.PutUint32(addr, i)
return ToIPV4Address(addr)
}
func ToChunkedBytesF(b []byte, size int, f func([]byte)) {
r := NewReader(b)
for r.Len() >= size {
f(r.ReadBytes(size))
}
if r.Len() > 0 {
f(r.ReadAvailable())
}
}
func ToBytes(i interface{}) []byte {
return NewWriterF(func(w *Writer) {
// TODO: more types
switch t := i.(type) {
case int16:
w.WriteUInt16(uint16(t))
case int32:
w.WriteUInt32(uint32(t))
}
})
}

112
binary/writer.go Normal file
View File

@ -0,0 +1,112 @@
package binary
import (
"bytes"
"encoding/binary"
)
type Writer struct {
buf *bytes.Buffer
}
func NewWriter() *Writer {
return &Writer{buf: new(bytes.Buffer)}
}
func NewWriterF(f func(writer *Writer)) []byte {
w := NewWriter()
f(w)
return w.Bytes()
}
func (w *Writer) Write(b []byte) {
w.buf.Write(b)
}
func (w *Writer) WriteByte(b byte) {
w.buf.WriteByte(b)
}
func (w *Writer) WriteUInt16(v uint16) {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, v)
w.Write(b)
}
func (w *Writer) WriteUInt32(v uint32) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, v)
w.Write(b)
}
func (w *Writer) WriteUInt64(v uint64) {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, v)
w.Write(b)
}
func (w *Writer) WriteString(v string) {
payload := []byte(v)
w.WriteUInt32(uint32(len(payload) + 4))
w.Write(payload)
}
func (w *Writer) WriteStringShort(v string) {
w.WriteTlv([]byte(v))
}
func (w *Writer) WriteBool(b bool) {
if b {
w.WriteByte(0x01)
} else {
w.WriteByte(0x00)
}
}
func (w *Writer) EncryptAndWrite(key []byte, data []byte) {
tea := NewTeaCipher(key)
ed := tea.Encrypt(data)
w.Write(ed)
}
func (w *Writer) WriteIntLvPacket(offset int, f func(writer *Writer)) {
t := NewWriter()
f(t)
data := t.Bytes()
w.WriteUInt32(uint32(len(data) + offset))
w.Write(data)
}
func (w *Writer) WriteUniPacket(commandName string, sessionId, extraData, body []byte) {
w.WriteIntLvPacket(4, func(w *Writer) {
w.WriteString(commandName)
w.WriteUInt32(8)
w.Write(sessionId)
if len(extraData) == 0 {
w.WriteUInt32(0x04)
} else {
w.WriteUInt32(uint32(len(extraData) + 4))
w.Write(extraData)
}
})
w.WriteIntLvPacket(4, func(w *Writer) {
w.Write(body)
})
}
func (w *Writer) WriteTlv(data []byte) {
w.WriteUInt16(uint16(len(data)))
w.Write(data)
}
func (w *Writer) WriteTlvLimitedSize(data []byte, limit int) {
if len(data) <= limit {
w.WriteTlv(data)
return
}
w.WriteTlv(data[:limit])
}
func (w *Writer) Bytes() []byte {
return w.buf.Bytes()
}

418
client/builders.go Normal file
View File

@ -0,0 +1,418 @@
package client
import (
"crypto/md5"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/crypto"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/protocol/tlv"
"github.com/golang/protobuf/proto"
"math/rand"
"strconv"
)
func (c *QQClient) buildLoginPacket() (uint16, []byte) {
seq := c.nextSeq()
req := packets.BuildOicqRequestPacket(c.Uin, 0x0810, crypto.ECDH, c.RandomKey, func(w *binary.Writer) {
w.WriteUInt16(9)
w.WriteUInt16(17)
w.Write(tlv.T18(16, uint32(c.Uin)))
w.Write(tlv.T1(uint32(c.Uin), SystemDeviceInfo.IpAddress))
w.Write(tlv.T106(uint32(c.Uin), 0, c.PasswordMd5, true, SystemDeviceInfo.Guid, SystemDeviceInfo.TgtgtKey))
w.Write(tlv.T116(184024956, 0x10400))
w.Write(tlv.T100())
w.Write(tlv.T107(0))
w.Write(tlv.T142("com.tencent.mobileqq"))
w.Write(tlv.T144(
SystemDeviceInfo.AndroidId,
SystemDeviceInfo.GenDeviceInfoData(),
SystemDeviceInfo.OSType,
SystemDeviceInfo.Version.Release,
SystemDeviceInfo.SimInfo,
SystemDeviceInfo.APN,
false, true, false, tlv.GuidFlag(),
SystemDeviceInfo.Model,
SystemDeviceInfo.Guid,
SystemDeviceInfo.Brand,
SystemDeviceInfo.TgtgtKey,
))
w.Write(tlv.T145(SystemDeviceInfo.Guid))
w.Write(tlv.T147(16, []byte("8.2.7"), []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}))
/*
if (miscBitMap & 0x80) != 0{
w.Write(tlv.T166(1))
}
*/
w.Write(tlv.T154(seq))
w.Write(tlv.T141(SystemDeviceInfo.SimInfo, SystemDeviceInfo.APN))
w.Write(tlv.T8(2052))
w.Write(tlv.T511([]string{
"tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com",
"qzone.qq.com", "vip.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com",
"office.qq.com", "ti.qq.com", "mail.qq.com", "qzone.com", "mma.qq.com",
}))
w.Write(tlv.T187(SystemDeviceInfo.MacAddress))
w.Write(tlv.T188(SystemDeviceInfo.AndroidId))
if len(SystemDeviceInfo.IMSIMd5) != 0 {
w.Write(tlv.T194(SystemDeviceInfo.IMSIMd5))
}
w.Write(tlv.T191(0x82))
if len(SystemDeviceInfo.WifiBSSID) != 0 && len(SystemDeviceInfo.WifiSSID) != 0 {
w.Write(tlv.T202(SystemDeviceInfo.WifiBSSID, SystemDeviceInfo.WifiSSID))
}
w.Write(tlv.T177())
w.Write(tlv.T516())
w.Write(tlv.T521())
w.Write(tlv.T525(tlv.T536([]byte{0x01, 0x00})))
})
sso := packets.BuildSsoPacket(seq, "wtlogin.login", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, req, c.ksid)
packet := packets.BuildLoginPacket(c.Uin, 2, make([]byte, 16), sso, []byte{})
return seq, packet
}
func (c *QQClient) buildCaptchaPacket(result string, sign []byte) (uint16, []byte) {
seq := c.nextSeq()
req := packets.BuildOicqRequestPacket(c.Uin, 0x810, crypto.ECDH, c.RandomKey, func(w *binary.Writer) {
w.WriteUInt16(2) // sub command
w.WriteUInt16(4)
w.Write(tlv.T2(result, sign))
w.Write(tlv.T8(2052))
w.Write(tlv.T104(c.t104))
w.Write(tlv.T116(150470524, 66560))
})
sso := packets.BuildSsoPacket(seq, "wtlogin.login", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, req, c.ksid)
packet := packets.BuildLoginPacket(c.Uin, 2, make([]byte, 16), sso, []byte{})
return seq, packet
}
func (c *QQClient) buildClientRegisterPacket() (uint16, []byte) {
seq := c.nextSeq()
svc := &jce.SvcReqRegister{
ConnType: 0,
Uin: c.Uin,
Bid: 1 | 2 | 4,
Status: 11,
KickPC: 0,
KickWeak: 0,
IOSVersion: int64(SystemDeviceInfo.Version.Sdk),
NetType: 1,
RegType: 0,
Guid: SystemDeviceInfo.Guid,
IsSetStatus: 0,
LocaleId: 2052,
DevName: string(SystemDeviceInfo.Model),
DevType: string(SystemDeviceInfo.Model),
OSVer: string(SystemDeviceInfo.Version.Release),
OpenPush: 1,
LargeSeq: 1551,
OldSSOIp: 0,
NewSSOIp: 31806887127679168,
ChannelNo: "",
CPID: 0,
VendorName: "MIUI",
VendorOSName: "ONEPLUS A5000_23_17",
B769: []byte{0x0A, 0x04, 0x08, 0x2E, 0x10, 0x00, 0x0A, 0x05, 0x08, 0x9B, 0x02, 0x10, 0x00},
SetMute: 0,
}
b := append([]byte{0x0A}, svc.ToBytes()...)
b = append(b, 0x0B)
buf := &jce.RequestDataVersion3{
Map: map[string][]byte{"SvcReqRegister": b},
}
pkt := &jce.RequestPacket{
IVersion: 3,
SServantName: "PushService",
SFuncName: "SvcReqRegister",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
sso := packets.BuildSsoPacket(seq, "StatSvc.register", SystemDeviceInfo.IMEI, c.sigInfo.tgt, c.OutGoingPacketSessionId, pkt.ToBytes(), c.ksid)
packet := packets.BuildLoginPacket(c.Uin, 1, c.sigInfo.d2Key, sso, c.sigInfo.d2)
return seq, packet
}
func (c *QQClient) buildPushResponsePacket(t int32, pktSeq int64, jceBuf []byte) (uint16, []byte) {
seq := c.nextSeq()
req := jce.NewJceWriter()
req.WriteInt32(t, 1)
req.WriteInt64(pktSeq, 2)
req.WriteBytes(jceBuf, 3)
b := append([]byte{0x0A}, req.Bytes()...)
b = append(b, 0x0B)
buf := &jce.RequestDataVersion3{
Map: map[string][]byte{"PushResp": b},
}
pkt := &jce.RequestPacket{
IVersion: 3,
SServantName: "QQService.ConfigPushSvc.MainServant",
SFuncName: "PushResp",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "ConfigPushSvc.PushResp", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
func (c *QQClient) buildFriendGroupListRequestPacket(friendStartIndex, friendListCount, groupStartIndex, groupListCount int16) (uint16, []byte) {
seq := c.nextSeq()
d50, _ := proto.Marshal(&pb.D50ReqBody{
Appid: 1002,
ReqMusicSwitch: 1,
ReqMutualmarkAlienation: 1,
ReqKsingSwitch: 1,
ReqMutualmarkLbsshare: 1,
})
req := &jce.FriendListRequest{
Reqtype: 3,
IfReflush: func() byte { // fuck golang
if friendStartIndex <= 0 {
return 0
}
return 1
}(),
Uin: int64(c.Uin),
StartIndex: friendStartIndex,
FriendCount: friendListCount,
GroupId: 0,
IfGetGroupInfo: func() byte {
if groupListCount <= 0 {
return 0
}
return 1
}(),
GroupStartIndex: byte(groupStartIndex),
GroupCount: byte(groupListCount),
IfGetMSFGroup: 0,
IfShowTermType: 1,
Version: 27,
UinList: nil,
AppType: 0,
IfGetDOVId: 0,
IfGetBothFlag: 0,
D50: d50,
D6B: []byte{},
SnsTypeList: []int64{13580, 13581, 13582},
}
b := append([]byte{0x0A}, req.ToBytes()...)
b = append(b, 0x0B)
buf := &jce.RequestDataVersion3{
Map: map[string][]byte{"FL": b},
}
pkt := &jce.RequestPacket{
IVersion: 3,
CPacketType: 0x003,
IRequestId: 1921334514,
SServantName: "mqq.IMService.FriendListServiceServantObj",
SFuncName: "GetFriendListReq",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "friendlist.getFriendGroupList", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
func (c *QQClient) buildGroupListRequestPacket() (uint16, []byte) {
seq := c.nextSeq()
req := &jce.TroopListRequest{
Uin: c.Uin,
GetMSFMsgFlag: 1,
Cookies: []byte{},
GroupInfo: []int64{},
GroupFlagExt: 1,
Version: 7,
CompanyId: 0,
VersionNum: 1,
GetLongGroupName: 1,
}
b := append([]byte{0x0A}, req.ToBytes()...)
b = append(b, 0x0B)
buf := &jce.RequestDataVersion3{
Map: map[string][]byte{"GetTroopListReqV2Simplify": b},
}
pkt := &jce.RequestPacket{
IVersion: 3,
CPacketType: 0x00,
IRequestId: c.nextPacketSeq(),
SServantName: "mqq.IMService.FriendListServiceServantObj",
SFuncName: "GetTroopListReqV2Simplify",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "friendlist.GetTroopListReqV2", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
func (c *QQClient) buildGroupMemberListRequestPacket(groupUin, groupCode, nextUin int64) (uint16, []byte) {
seq := c.nextSeq()
req := &jce.TroopMemberListRequest{
Uin: c.Uin,
GroupCode: groupCode,
NextUin: nextUin,
GroupUin: groupUin,
Version: 2,
}
b := append([]byte{0x0A}, req.ToBytes()...)
b = append(b, 0x0B)
buf := &jce.RequestDataVersion3{
Map: map[string][]byte{"GTML": b},
}
pkt := &jce.RequestPacket{
IVersion: 3,
IRequestId: c.nextPacketSeq(),
SServantName: "mqq.IMService.FriendListServiceServantObj",
SFuncName: "GetTroopMemberListReq",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "friendlist.GetTroopMemberListReq", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
func (c *QQClient) buildGetMessageRequestPacket(flag msg.SyncFlag, msgTime int64) (uint16, []byte) {
seq := c.nextSeq()
cook := c.syncCookie
if cook == nil {
cook, _ = proto.Marshal(&msg.SyncCookie{
Time: msgTime,
Ran1: 758330138,
Ran2: 2480149246,
Const1: 1167238020,
Const2: 3913056418,
Const3: 0x1D,
})
}
req := &msg.GetMessageRequest{
SyncFlag: flag,
SyncCookie: cook,
LatestRambleNumber: 20,
OtherRambleNumber: 3,
OnlineSyncFlag: 1,
ContextFlag: 1,
MsgReqType: 1,
PubaccountCookie: []byte{},
MsgCtrlBuf: []byte{},
ServerBuf: []byte{},
}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "MessageSvc.PbGetMsg", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildStopGetMessagePacket(msgTime int64) []byte {
_, pkt := c.buildGetMessageRequestPacket(msg.SyncFlag_STOP, msgTime)
return pkt
}
func (c *QQClient) buildDeleteMessageRequestPacket(msg []*pb.MessageItem) (uint16, []byte) {
seq := c.nextSeq()
req := &pb.DeleteMessageRequest{Items: msg}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "MessageSvc.PbDeleteMsg", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildGroupSendingPacket(groupCode int64, m *message.SendingMessage) (uint16, []byte) {
seq := c.nextSeq()
req := &msg.SendMessageRequest{
RoutingHead: &msg.RoutingHead{Grp: &msg.Grp{GroupCode: groupCode}},
ContentHead: &msg.ContentHead{PkgNum: 1},
MsgBody: &msg.MessageBody{
RichText: &msg.RichText{
Elems: message.ToProtoElems(m.Elements),
},
},
MsgSeq: c.nextMessageSeq(),
MsgRand: int32(rand.Uint32()),
SyncCookie: EmptyBytes,
MsgVia: 1,
MsgCtrl: nil,
}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "MessageSvc.PbSendMsg", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildGroupImageStorePacket(groupCode int64, md5 [16]byte, size int32) (uint16, []byte) {
seq := c.nextSeq()
name := binary.RandomString(16) + ".gif"
req := &pb.D388ReqBody{
NetType: 3,
Subcmd: 1,
MsgTryupImgReq: []*pb.TryUpImgReq{
{
GroupCode: groupCode,
SrcUin: c.Uin,
FileMd5: md5[:],
FileSize: int64(size),
FileName: name,
SrcTerm: 5,
PlatformType: 9,
BuType: 1,
PicType: 1000,
BuildVer: "8.2.7.4410",
AppPicType: 1006,
FileIndex: EmptyBytes,
TransferUrl: EmptyBytes,
},
},
Extension: EmptyBytes,
}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "ImgStore.GroupPicUp", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildImageUploadPacket(data, updKey []byte, commandId int32, fmd5 [16]byte) (r [][]byte) {
offset := 0
binary.ToChunkedBytesF(data, 8192*8, func(chunked []byte) {
w := binary.NewWriter()
cmd5 := md5.Sum(chunked)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.DataUp",
Seq: func() int32 {
if commandId == 2 {
return c.nextGroupDataTransSeq()
}
return c.nextGroupDataTransSeq()
}(),
Appid: 537062409,
Dataflag: 4096,
CommandId: commandId,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: int64(len(data)),
Dataoffset: int64(offset),
Datalength: int32(len(chunked)),
Serviceticket: updKey,
Md5: cmd5[:],
FileMd5: fmd5[:],
},
ReqExtendinfo: EmptyBytes,
})
offset += len(chunked)
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(chunked)))
w.Write(head)
w.Write(chunked)
w.WriteByte(41)
r = append(r, w.Bytes())
})
return
}

409
client/client.go Normal file
View File

@ -0,0 +1,409 @@
package client
import (
"crypto/md5"
"errors"
"fmt"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/golang/protobuf/proto"
"log"
"math/rand"
"net"
"strconv"
"sync/atomic"
"time"
)
type QQClient struct {
Uin int64
PasswordMd5 [16]byte
Nickname string
Age uint16
Gender uint16
FriendList []*FriendInfo
GroupList []*GroupInfo
SequenceId uint16
OutGoingPacketSessionId []byte
RandomKey []byte
Conn net.Conn
decoders map[string]func(*QQClient, []byte) (interface{}, error)
handlers map[uint16]func(interface{}, error)
syncCookie []byte
pubAccountCookie []byte
msgCtrlBuf []byte
ksid []byte
t104 []byte
t150 []byte
t149 []byte
t528 []byte
t530 []byte
rollbackSig []byte
timeDiff int64
sigInfo *loginSigInfo
pwdFlag bool
running bool
lastMessageSeq int32
requestPacketRequestId int32
messageSeq int32
groupDataTransSeq int32
privateMessageHandlers []func(*QQClient, *message.PrivateMessage)
groupMessageHandlers []func(*QQClient, *message.GroupMessage)
}
type loginSigInfo struct {
loginBitmap uint64
tgt []byte
tgtKey []byte
userStKey []byte
userStWebSig []byte
sKey []byte
d2 []byte
d2Key []byte
wtSessionTicketKey []byte
deviceToken []byte
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// NewClient create new qq client
func NewClient(uin int64, password string) *QQClient {
cli := &QQClient{
Uin: uin,
PasswordMd5: md5.Sum([]byte(password)),
SequenceId: 0x3635,
RandomKey: make([]byte, 16),
OutGoingPacketSessionId: []byte{0x02, 0xB0, 0x5B, 0x8B},
decoders: map[string]func(*QQClient, []byte) (interface{}, error){
"wtlogin.login": decodeLoginResponse,
"StatSvc.register": decodeClientRegisterResponse,
"MessageSvc.PushNotify": decodeSvcNotify,
"OnlinePush.PbPushGroupMsg": decodeGroupMessagePacket,
"ConfigPushSvc.PushReq": decodePushReqPacket,
"MessageSvc.PbGetMsg": decodeMessageSvcPacket,
"friendlist.getFriendGroupList": decodeFriendGroupListResponse,
"friendlist.GetTroopListReqV2": decodeGroupListResponse,
"friendlist.GetTroopMemberListReq": decodeGroupMemberListResponse,
"ImgStore.GroupPicUp": decodeGroupImageStoreResponse,
},
handlers: map[uint16]func(interface{}, error){},
sigInfo: &loginSigInfo{},
requestPacketRequestId: 1921334513,
messageSeq: 22911,
ksid: []byte("|454001228437590|A8.2.7.27f6ea96"),
}
rand.Read(cli.RandomKey)
return cli
}
// Login send login request
func (c *QQClient) Login() (*LoginResponse, error) {
if c.running {
return nil, ErrAlreadyRunning
}
conn, err := net.Dial("tcp", "125.94.60.146:80") //TODO: more servers
if err != nil {
return nil, err
}
c.Conn = conn
c.running = true
go c.loop()
seq, packet := c.buildLoginPacket()
rsp, err := c.sendAndWait(seq, packet)
if err != nil {
return nil, err
}
l := rsp.(LoginResponse)
if l.Success {
c.registerClient()
go c.heartbeat()
}
return &l, nil
}
// SubmitCaptcha send captcha to server
func (c *QQClient) SubmitCaptcha(result string, sign []byte) (*LoginResponse, error) {
seq, packet := c.buildCaptchaPacket(result, sign)
rsp, err := c.sendAndWait(seq, packet)
if err != nil {
return nil, err
}
l := rsp.(LoginResponse)
if l.Success {
c.registerClient()
go c.heartbeat()
}
return &l, nil
}
// ReloadFriendList refresh QQClient.FriendList field via GetFriendList()
func (c *QQClient) ReloadFriendList() error {
rsp, err := c.GetFriendList()
if err != nil {
return err
}
c.FriendList = rsp.List
return nil
}
// GetFriendList request friend list
func (c *QQClient) GetFriendList() (*FriendListResponse, error) {
var curFriendCount = 0
r := &FriendListResponse{}
for {
rsp, err := c.sendAndWait(c.buildFriendGroupListRequestPacket(int16(curFriendCount), 150, 0, 0))
if err != nil {
return nil, err
}
list := rsp.(FriendListResponse)
r.TotalCount = list.TotalCount
r.List = append(r.List, list.List...)
curFriendCount += len(list.List)
if int32(curFriendCount) >= r.TotalCount {
break
}
}
return r, nil
}
func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage) {
_, pkt := c.buildGroupSendingPacket(groupCode, m)
c.send(pkt)
}
func (c *QQClient) UploadGroupImage(groupCode int64, img []byte) (*message.GroupImageElement, error) {
h := md5.Sum(img)
seq, pkt := c.buildGroupImageStorePacket(groupCode, h, int32(len(img)))
r, err := c.sendAndWait(seq, pkt)
if err != nil {
return nil, err
}
rsp := r.(groupImageUploadResponse)
if rsp.ResultCode != 0 {
return nil, errors.New(rsp.Message)
}
if rsp.IsExists {
return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil
}
for i, ip := range rsp.UploadIp {
updServer := binary.UInt32ToIPV4Address(uint32(ip))
conn, err := net.DialTimeout("tcp", updServer+":"+strconv.FormatInt(int64(rsp.UploadPort[i]), 10), time.Second*5)
if err != nil {
continue
}
if conn.SetDeadline(time.Now().Add(time.Second*10)) != nil {
_ = conn.Close()
continue
}
pkt := c.buildImageUploadPacket(img, rsp.UploadKey, 2, h)
for _, p := range pkt {
_, err = conn.Write(p)
}
if err != nil {
continue
}
r := binary.NewNetworkReader(conn)
r.ReadByte()
hl := r.ReadInt32()
r.ReadBytes(4)
payload := r.ReadBytes(int(hl))
_ = conn.Close()
rsp := pb.RspDataHighwayHead{}
if proto.Unmarshal(payload, &rsp) != nil {
continue
}
if rsp.ErrorCode != 0 {
return nil, errors.New("upload failed")
}
return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil
}
return nil, errors.New("upload failed")
}
func (c *QQClient) ReloadGroupList() error {
list, err := c.GetGroupList()
if err != nil {
return err
}
c.GroupList = list
return nil
}
func (c *QQClient) GetGroupList() ([]*GroupInfo, error) {
rsp, err := c.sendAndWait(c.buildGroupListRequestPacket())
if err != nil {
return nil, err
}
r := rsp.([]*GroupInfo)
for _, group := range r {
m, err := c.GetGroupMembers(group)
if err != nil {
continue
}
group.Members = m
}
return r, nil
}
func (c *QQClient) GetGroupMembers(group *GroupInfo) ([]GroupMemberInfo, error) {
var nextUin int64
var list []GroupMemberInfo
for {
data, err := c.sendAndWait(c.buildGroupMemberListRequestPacket(group.Uin, group.Code, nextUin))
if err != nil {
return nil, err
}
rsp := data.(groupMemberListResponse)
nextUin = rsp.NextUin
list = append(list, rsp.list...)
if nextUin == 0 {
return list, nil
}
}
}
func (c *QQClient) FindFriend(uin int64) *FriendInfo {
for _, t := range c.FriendList {
f := t
if f.Uin == uin {
return f
}
}
return nil
}
func (c *QQClient) FindGroup(uin int64) *GroupInfo {
for _, g := range c.GroupList {
f := g
if f.Uin == uin {
return f
}
}
return nil
}
func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo {
for _, m := range g.Members {
f := m
if f.Uin == uin {
return &f
}
}
return nil
}
func (c *QQClient) registerClient() {
seq, packet := c.buildClientRegisterPacket()
_, _ = c.sendAndWait(seq, packet)
}
func (c *QQClient) nextSeq() uint16 {
c.SequenceId++
c.SequenceId &= 0x7FFF
if c.SequenceId == 0 {
c.SequenceId++
}
return c.SequenceId
}
func (c *QQClient) nextPacketSeq() int32 {
s := atomic.LoadInt32(&c.requestPacketRequestId)
atomic.AddInt32(&c.requestPacketRequestId, 2)
return s
}
func (c *QQClient) nextMessageSeq() int32 {
s := atomic.LoadInt32(&c.messageSeq)
atomic.AddInt32(&c.messageSeq, 2)
return s
}
func (c *QQClient) nextGroupDataTransSeq() int32 {
s := atomic.LoadInt32(&c.groupDataTransSeq)
atomic.AddInt32(&c.groupDataTransSeq, 2)
return s
}
func (c *QQClient) send(pkt []byte) error {
_, err := c.Conn.Write(pkt)
return err
}
func (c *QQClient) sendAndWait(seq uint16, pkt []byte) (interface{}, error) {
type T struct {
Response interface{}
Error error
}
_, err := c.Conn.Write(pkt)
if err != nil {
return nil, err
}
ch := make(chan T)
c.handlers[seq] = func(i interface{}, err error) {
ch <- T{
Response: i,
Error: err,
}
}
rsp := <-ch
return rsp.Response, rsp.Error
}
func (c *QQClient) loop() {
reader := binary.NewNetworkReader(c.Conn)
for c.running {
data := reader.ReadBytes(int(reader.ReadInt32()) - 4)
pkt, err := packets.ParseIncomingPacket(data, c.sigInfo.d2Key)
if err != nil {
log.Println("parse incoming packet error: " + err.Error())
continue
}
payload := pkt.Payload
if pkt.Flag2 == 2 {
payload, err = pkt.DecryptPayload(c.RandomKey)
if err != nil {
continue
}
}
fmt.Println(pkt.CommandName)
go func() {
decoder, ok := c.decoders[pkt.CommandName]
if !ok {
if f, ok := c.handlers[pkt.SequenceId]; ok {
delete(c.handlers, pkt.SequenceId)
f(nil, nil)
}
return
}
rsp, err := decoder(c, payload)
if err != nil {
log.Println("decode", pkt.CommandName, "error:", err)
}
if f, ok := c.handlers[pkt.SequenceId]; ok {
delete(c.handlers, pkt.SequenceId)
f(rsp, err)
}
}()
}
}
func (c *QQClient) heartbeat() {
for c.running {
time.Sleep(time.Second * 30)
seq := c.nextSeq()
sso := packets.BuildSsoPacket(seq, "Heartbeat.Alive", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, []byte{}, c.ksid)
packet := packets.BuildLoginPacket(c.Uin, 0, []byte{}, sso, []byte{})
_, _ = c.sendAndWait(seq, packet)
}
}

282
client/decoders.go Normal file
View File

@ -0,0 +1,282 @@
package client
import (
"errors"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/golang/protobuf/proto"
"time"
)
func decodeLoginResponse(c *QQClient, payload []byte) (interface{}, error) {
reader := binary.NewReader(payload)
reader.ReadUInt16() // sub command
t := reader.ReadByte()
reader.ReadUInt16()
m := reader.ReadTlvMap(2)
if t == 0 { // login success
if t150, ok := m[0x150]; ok {
c.t150 = t150
}
if t161, ok := m[0x161]; ok {
c.decodeT161(t161)
}
c.decodeT119(m[0x119])
return LoginResponse{
Success: true,
}, nil
}
if t == 2 {
c.t104, _ = m[0x104]
if m.Exists(0x192) { // slider, not supported yet
return LoginResponse{
Success: false,
Error: UnknownLoginError,
}, nil
}
if m.Exists(0x165) { // image
imgData := binary.NewReader(m[0x105])
signLen := imgData.ReadUInt16()
imgData.ReadUInt16()
sign := imgData.ReadBytes(int(signLen))
return LoginResponse{
Success: false,
Error: NeedCaptcha,
CaptchaImage: imgData.ReadAvailable(),
CaptchaSign: sign,
}, nil
} else {
return LoginResponse{
Success: false,
Error: UnknownLoginError,
}, nil
}
} // need captcha
if t == 160 {
}
if t == 204 {
return LoginResponse{
Success: false,
Error: DeviceLockError,
}, nil
} // drive lock
if t149, ok := m[0x149]; ok {
t149r := binary.NewReader(t149)
t149r.ReadBytes(2)
t149r.ReadStringShort() // title
return LoginResponse{
Success: false,
Error: OtherLoginError,
ErrorMessage: t149r.ReadStringShort(),
}, nil
}
if t146, ok := m[0x146]; ok {
t146r := binary.NewReader(t146)
t146r.ReadBytes(4) // ver and code
t146r.ReadStringShort() // title
return LoginResponse{
Success: false,
Error: OtherLoginError,
ErrorMessage: t146r.ReadStringShort(),
}, nil
}
return nil, nil // ?
}
func decodeClientRegisterResponse(c *QQClient, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
return nil, nil
}
func decodePushReqPacket(c *QQClient, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
r := jce.NewJceReader(data.Map["PushReq"]["ConfigPush.PushReq"][1:])
jceBuf := []byte{}
t := r.ReadInt32(1)
r.ReadSlice(&jceBuf, 2)
seq := r.ReadInt64(3)
_, pkt := c.buildPushResponsePacket(t, seq, jceBuf)
return nil, c.send(pkt)
}
func decodeMessageSvcPacket(c *QQClient, payload []byte) (interface{}, error) {
rsp := msg.GetMessageResponse{}
err := proto.Unmarshal(payload, &rsp)
if err != nil {
return nil, err
}
if rsp.Result != 0 {
return nil, errors.New("message svc result unsuccessful")
}
c.syncCookie = rsp.SyncCookie
c.pubAccountCookie = rsp.PubAccountCookie
c.msgCtrlBuf = rsp.MsgCtrlBuf
if rsp.UinPairMsgs == nil {
return nil, nil
}
var delItems []*pb.MessageItem
for _, pairMsg := range rsp.UinPairMsgs {
for _, message := range pairMsg.Messages {
// delete message
delItem := &pb.MessageItem{
FromUin: message.Head.FromUin,
ToUin: message.Head.ToUin,
MsgType: 187,
MsgSeq: message.Head.MsgSeq,
MsgUid: message.Head.MsgUid,
}
delItems = append(delItems, delItem)
if message.Head.ToUin != c.Uin {
continue
}
if message.Body.RichText == nil || message.Body.RichText.Elems == nil {
continue
}
if c.lastMessageSeq >= message.Head.MsgSeq {
continue
}
c.lastMessageSeq = message.Head.MsgSeq
c.dispatchFriendMessage(c.parsePrivateMessage(message))
}
}
_, _ = c.sendAndWait(c.buildDeleteMessageRequestPacket(delItems))
if rsp.SyncFlag != msg.SyncFlag_STOP {
seq, pkt := c.buildGetMessageRequestPacket(rsp.SyncFlag, time.Now().Unix())
_, _ = c.sendAndWait(seq, pkt)
}
return nil, err
}
func decodeGroupMessagePacket(c *QQClient, payload []byte) (interface{}, error) {
pkt := msg.PushMessagePacket{}
err := proto.Unmarshal(payload, &pkt)
if err != nil {
return nil, err
}
if pkt.Message.Head.FromUin == c.Uin {
return nil, nil
}
c.dispatchGroupMessage(c.parseGroupMessage(pkt.Message))
return nil, nil
}
func decodeSvcNotify(c *QQClient, payload []byte) (interface{}, error) {
_, pkt := c.buildGetMessageRequestPacket(msg.SyncFlag_START, time.Now().Unix())
return nil, c.send(pkt)
}
func decodeFriendGroupListResponse(c *QQClient, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion3{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
r := jce.NewJceReader(data.Map["FLRESP"][1:])
totalFriendCount := r.ReadInt16(5)
friends := []jce.FriendInfo{}
r.ReadSlice(&friends, 7)
var l []*FriendInfo
for _, f := range friends {
l = append(l, &FriendInfo{
Uin: f.FriendUin,
Nickname: f.Nick,
Remark: f.Remark,
FaceId: f.FaceId,
})
}
rsp := FriendListResponse{
TotalCount: int32(totalFriendCount),
List: l,
}
return rsp, nil
}
func decodeGroupListResponse(c *QQClient, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion3{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
r := jce.NewJceReader(data.Map["GetTroopListRespV2"][1:])
groups := []jce.TroopNumber{}
r.ReadSlice(&groups, 5)
var l []*GroupInfo
for _, g := range groups {
l = append(l, &GroupInfo{
Uin: g.GroupUin,
Code: g.GroupCode,
Name: g.GroupName,
Memo: g.GroupMemo,
OwnerUin: uint32(g.GroupOwnerUin),
MemberCount: uint16(g.MemberNum),
MaxMemberCount: uint16(g.MaxGroupMemberNum),
})
}
return l, nil
}
func decodeGroupMemberListResponse(c *QQClient, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion3{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
r := jce.NewJceReader(data.Map["GTMLRESP"][1:])
members := []jce.TroopMemberInfo{}
r.ReadSlice(&members, 3)
next := r.ReadInt64(4)
var l []GroupMemberInfo
for _, m := range members {
l = append(l, GroupMemberInfo{
Uin: m.MemberUin,
Nickname: m.Nick,
CardName: m.Name,
Level: uint16(m.MemberLevel),
JoinTime: m.JoinTime,
LastSpeakTime: m.LastSpeakTime,
SpecialTitle: m.SpecialTitle,
SpecialTitleExpireTime: m.SpecialTitleExpireTime,
Job: m.Job,
})
}
return groupMemberListResponse{
NextUin: next,
list: l,
}, nil
}
func decodeGroupImageStoreResponse(c *QQClient, payload []byte) (interface{}, error) {
pkt := pb.D388RespBody{}
err := proto.Unmarshal(payload, &pkt)
if err != nil {
return nil, err
}
rsp := pkt.MsgTryupImgRsp[0]
if rsp.Result != 0 {
return groupImageUploadResponse{
ResultCode: rsp.Result,
Message: rsp.FailMsg,
}, nil
}
if rsp.BoolFileExit {
return groupImageUploadResponse{IsExists: true}, nil
}
return groupImageUploadResponse{
UploadKey: rsp.UpUkey,
UploadIp: rsp.Uint32UpIp,
UploadPort: rsp.Uint32UpPort,
}, nil
}

80
client/entities.go Normal file
View File

@ -0,0 +1,80 @@
package client
import "errors"
var (
ErrAlreadyRunning = errors.New("already running")
)
type (
LoginError int
LoginResponse struct {
Success bool
Error LoginError
// Captcha info
CaptchaImage []byte
CaptchaSign []byte
// other error
ErrorMessage string
}
FriendInfo struct {
Uin int64
Nickname string
Remark string
FaceId int16
}
FriendListResponse struct {
TotalCount int32
List []*FriendInfo
}
GroupInfo struct {
Uin int64
Code int64
Name string
Memo string
OwnerUin uint32
MemberCount uint16
MaxMemberCount uint16
Members []GroupMemberInfo
}
GroupMemberInfo struct {
Uin int64
Nickname string
CardName string
Level uint16
JoinTime int64
LastSpeakTime int64
SpecialTitle string
SpecialTitleExpireTime int64
Job string
}
groupMemberListResponse struct {
NextUin int64
list []GroupMemberInfo
}
groupImageUploadResponse struct {
ResultCode int32
Message string
IsExists bool
UploadKey []byte
UploadIp []int32
UploadPort []int32
}
)
const (
NeedCaptcha LoginError = 1
DeviceLockError = 2
OtherLoginError = 3
UnknownLoginError = -1
)

57
client/events.go Normal file
View File

@ -0,0 +1,57 @@
package client
import "github.com/Mrs4s/MiraiGo/message"
func (c *QQClient) OnPrivateMessage(f func(*QQClient, *message.PrivateMessage)) {
c.privateMessageHandlers = append(c.privateMessageHandlers, f)
}
func (c *QQClient) OnPrivateMessageF(filter func(*message.PrivateMessage) bool, f func(*QQClient, *message.PrivateMessage)) {
c.privateMessageHandlers = append(c.privateMessageHandlers, func(client *QQClient, msg *message.PrivateMessage) {
if filter(msg) {
f(client, msg)
}
})
}
func (c *QQClient) OnGroupMessage(f func(*QQClient, *message.GroupMessage)) {
c.groupMessageHandlers = append(c.groupMessageHandlers, f)
}
func NewUinFilterPrivate(uin int64) func(*message.PrivateMessage) bool {
return func(msg *message.PrivateMessage) bool {
return msg.Sender.Uin == uin
}
}
func (c *QQClient) dispatchFriendMessage(msg *message.PrivateMessage) {
if msg == nil {
return
}
for _, f := range c.privateMessageHandlers {
func() {
defer func() {
if pan := recover(); pan != nil {
//
}
}()
f(c, msg)
}()
}
}
func (c *QQClient) dispatchGroupMessage(msg *message.GroupMessage) {
if msg == nil {
return
}
for _, f := range c.groupMessageHandlers {
func() {
defer func() {
if pan := recover(); pan != nil {
// TODO: logger
}
}()
f(c, msg)
}()
}
}

201
client/global.go Normal file
View File

@ -0,0 +1,201 @@
package client
import (
"crypto/md5"
devinfo "github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/message"
"google.golang.org/protobuf/proto"
"math/rand"
)
type DeviceInfo struct {
Display []byte
Product []byte
Device []byte
Board []byte
Brand []byte
Model []byte
Bootloader []byte
FingerPrint []byte
BootId []byte
ProcVersion []byte
BaseBand []byte
SimInfo []byte
OSType []byte
MacAddress []byte
IpAddress []byte
WifiBSSID []byte
WifiSSID []byte
IMSIMd5 []byte
IMEI string
AndroidId []byte
APN []byte
Guid []byte
TgtgtKey []byte
Version *Version
}
type Version struct {
Incremental []byte
Release []byte
CodeName []byte
Sdk uint32
}
var SystemDeviceInfo = &DeviceInfo{
Display: []byte("MIRAI.123456.001"),
Product: []byte("mirai"),
Device: []byte("mirai"),
Board: []byte("mirai"),
Brand: []byte("mamoe"),
Model: []byte("mirai"),
Bootloader: []byte("unknown"),
FingerPrint: []byte("mamoe/mirai/mirai:10/MIRAI.200122.001/1234567:user/release-keys"),
BootId: []byte("cb886ae2-00b6-4d68-a230-787f111d12c7"),
ProcVersion: []byte("Linux version 3.0.31-cb886ae2 (android-build@xxx.xxx.xxx.xxx.com)"),
BaseBand: []byte{},
SimInfo: []byte("T-Mobile"),
OSType: []byte("android"),
MacAddress: []byte("00:50:56:C0:00:08"),
IpAddress: []byte{10, 0, 1, 3}, // 10.0.1.3
WifiBSSID: []byte("00:50:56:C0:00:08"),
WifiSSID: []byte("<unknown ssid>"),
IMEI: "468356291846738",
AndroidId: []byte("MIRAI.123456.001"),
APN: []byte("wifi"),
Version: &Version{
Incremental: []byte("5891938"),
Release: []byte("10"),
CodeName: []byte("REL"),
Sdk: 29,
},
}
var EmptyBytes = []byte{}
func init() {
r := make([]byte, 16)
rand.Read(r)
t := md5.Sum(r)
SystemDeviceInfo.IMSIMd5 = t[:]
SystemDeviceInfo.GenNewGuid()
SystemDeviceInfo.GenNewTgtgtKey()
}
func (info *DeviceInfo) GenNewGuid() {
t := md5.Sum(append(info.AndroidId, info.MacAddress...))
info.Guid = t[:]
}
func (info *DeviceInfo) GenNewTgtgtKey() {
r := make([]byte, 16)
rand.Read(r)
t := md5.Sum(append(r, info.Guid...))
info.TgtgtKey = t[:]
}
func (info *DeviceInfo) GenDeviceInfoData() []byte {
msg := &devinfo.DeviceInfo{
Bootloader: string(info.Bootloader),
ProcVersion: string(info.ProcVersion),
Codename: string(info.Version.CodeName),
Incremental: string(info.Version.Incremental),
Fingerprint: string(info.FingerPrint),
BootId: string(info.BootId),
AndroidId: string(info.AndroidId),
BaseBand: string(info.BaseBand),
InnerVersion: string(info.Version.Incremental),
}
data, err := proto.Marshal(msg)
if err != nil {
panic(err)
}
return data
}
func (c *QQClient) parsePrivateMessage(msg *msg.Message) *message.PrivateMessage {
switch msg.Head.MsgType {
case 166:
friend := c.FindFriend(msg.Head.FromUin)
if friend == nil {
return nil
}
return &message.PrivateMessage{
Id: msg.Head.MsgSeq,
Sender: &message.Sender{
Uin: friend.Uin,
Nickname: friend.Nickname,
},
Elements: parseMessageElems(msg.Body.RichText.Elems),
}
default:
return nil
}
}
func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
group := c.FindGroup(m.Head.GroupInfo.GroupCode)
if group == nil {
return nil
}
var anonInfo *msg.AnonymousGroupMessage
for _, e := range m.Body.RichText.Elems {
if e.AnonGroupMsg != nil {
anonInfo = e.AnonGroupMsg
}
}
var sender *message.Sender
if anonInfo != nil {
sender = &message.Sender{
Uin: 80000000,
Nickname: string(anonInfo.AnonNick),
IsFriend: false,
}
} else {
mem := group.FindMember(m.Head.FromUin)
if mem == nil {
return nil
}
sender = &message.Sender{
Uin: mem.Uin,
Nickname: mem.Nickname,
CardName: mem.CardName,
IsFriend: c.FindFriend(mem.Uin) != nil,
}
}
g := &message.GroupMessage{
Id: m.Head.MsgSeq,
GroupUin: m.Head.GroupInfo.GroupCode,
GroupName: string(m.Head.GroupInfo.GroupName),
Sender: sender,
Elements: parseMessageElems(m.Body.RichText.Elems),
IsAdministrator: false,
}
return g
}
func parseMessageElems(elems []*msg.Elem) []message.IMessageElement {
var res []message.IMessageElement
for _, elem := range elems {
if elem.Text != nil {
res = append(res, message.NewText(elem.Text.Str))
}
if elem.CustomFace != nil {
res = append(res, message.NewNetImage(elem.CustomFace.FilePath, "http://gchat.qpic.cn/"+elem.CustomFace.OrigUrl))
}
if elem.NotOnlineImage != nil {
var img string
if elem.NotOnlineImage.OrigUrl != "" {
img = "http://c2cpicdw.qpic.cn" + elem.NotOnlineImage.OrigUrl
} else {
img = "http://c2cpicdw.qpic.cn/offpic_new/0/" + elem.NotOnlineImage.ResId + "/0?term=2"
}
res = append(res, message.NewNetImage(elem.NotOnlineImage.FilePath, img))
}
if elem.Face != nil {
res = append(res, message.NewFace(elem.Face.Index))
}
}
return res
}

1959
client/pb/data.pb.go Normal file

File diff suppressed because it is too large Load Diff

157
client/pb/data.proto Normal file
View File

@ -0,0 +1,157 @@
syntax = "proto3";
option go_package = ".;pb";
message DeviceInfo {
string bootloader = 1;
string procVersion = 2;
string codename = 3;
string incremental = 4;
string fingerprint = 5;
string bootId = 6;
string androidId = 7;
string baseBand = 8;
string innerVersion = 9;
}
message RequestBody {
repeated ConfigSeq rpt_config_list = 1;
}
message ConfigSeq {
int32 type = 1;
int32 version = 2;
}
message D50ReqBody {
int64 appid = 1;
int32 maxPkgSize = 2;
int32 startTime = 3;
int32 startIndex = 4;
int32 reqNum = 5;
repeated int64 uinList = 6;
int32 reqMusicSwitch = 91001;
int32 reqMutualmarkAlienation = 101001;
int32 reqMutualmarkScore = 141001;
int32 reqKsingSwitch = 151001;
int32 reqMutualmarkLbsshare = 181001;
}
message D388ReqBody {
int32 netType = 1;
int32 subcmd = 2;
repeated TryUpImgReq msgTryupImgReq = 3;
int32 commandId = 7;
bytes extension = 1001;
}
message D388RespBody {
int32 clientIp = 1;
int32 subCmd = 2;
repeated TryUpImgResp msgTryupImgRsp = 3;
}
message ReqDataHighwayHead {
DataHighwayHead msgBasehead = 1;
SegHead msgSeghead = 2;
bytes reqExtendinfo = 3;
int64 timestamp = 4;
//LoginSigHead? msgLoginSigHead = 5;
}
message RspDataHighwayHead {
DataHighwayHead msgBasehead = 1;
SegHead msgSeghead = 2;
int32 errorCode = 3;
int32 allowRetry = 4;
int32 cachecost = 5;
int32 htcost = 6;
bytes rspExtendinfo = 7;
int64 timestamp = 8;
int64 range = 9;
int32 isReset = 10;
}
message DataHighwayHead {
int32 version = 1;
string uin = 2;
string command = 3;
int32 seq = 4;
int32 retryTimes = 5;
int32 appid = 6;
int32 dataflag = 7;
int32 commandId = 8;
string buildVer = 9;
int32 localeId = 10;
}
message SegHead {
int32 serviceid = 1;
int64 filesize = 2;
int64 dataoffset = 3;
int32 datalength = 4;
int32 rtcode = 5;
bytes serviceticket = 6;
int32 flag = 7;
bytes md5 = 8;
bytes fileMd5 = 9;
int32 cacheAddr = 10;
int32 queryTimes = 11;
int32 updateCacheip = 12;
}
message TryUpImgReq {
int64 groupCode = 1;
int64 srcUin = 2;
int64 fileId = 3;
bytes fileMd5 = 4;
int64 fileSize = 5;
string fileName = 6;
int32 srcTerm = 7;
int32 platformType = 8;
int32 buType = 9;
int32 picWidth = 10;
int32 picHeight = 11;
int32 picType = 12;
string buildVer = 13;
int32 innerIp = 14;
int32 appPicType = 15;
int32 originalPic = 16;
bytes fileIndex = 17;
int64 dstUin = 18;
int32 srvUpload = 19;
bytes transferUrl = 20;
}
message TryUpImgResp {
int64 fileId = 1;
int32 result = 2;
string failMsg = 3;
bool boolFileExit = 4;
ImgInfo msgImgInfo = 5;
repeated int32 uint32UpIp = 6;
repeated int32 uint32UpPort = 7;
bytes upUkey = 8;
int64 fid = 9;
}
message ImgInfo {
bytes fileMd5 = 1;
int32 fileType = 2;
int64 fileSize = 3;
int32 fileWidth = 4;
int32 fileHeight = 5;
}
message DeleteMessageRequest {
repeated MessageItem items = 1;
}
message MessageItem {
int64 fromUin = 1;
int64 toUin = 2;
int32 msgType = 3;
int32 msgSeq = 4;
int64 msgUid = 5;
bytes sig = 7;
}

5402
client/pb/msg/msg.pb.go Normal file

File diff suppressed because it is too large Load Diff

519
client/pb/msg/msg.proto Normal file
View File

@ -0,0 +1,519 @@
syntax = "proto3";
option go_package = ".;msg";
message GetMessageRequest {
SyncFlag syncFlag = 1;
bytes syncCookie = 2;
int32 rambleFlag = 3;
int32 latestRambleNumber = 4;
int32 otherRambleNumber = 5;
int32 onlineSyncFlag = 6;
int32 contextFlag = 7;
int32 whisperSessionId = 8;
int32 msgReqType = 9;
bytes pubaccountCookie = 10;
bytes msgCtrlBuf = 11;
bytes serverBuf = 12;
}
message SendMessageRequest {
RoutingHead routingHead = 1;
ContentHead contentHead = 2;
MessageBody msgBody = 3;
int32 msgSeq = 4;
int32 msgRand = 5;
bytes syncCookie = 6;
//MsgComm.AppShareInfo? appShare = 7;
int32 msgVia = 8;
int32 dataStatist = 9;
//MultiMsgAssist? multiMsgAssist = 10;
//PbInputNotifyInfo? inputNotifyInfo = 11;
MsgCtrl msgCtrl = 12;
//ImReceipt.ReceiptReq? receiptReq = 13;
int32 multiSendSeq = 14;
}
message RoutingHead {
C2C c2c = 1;
Grp grp = 2;
GrpTmp grpTmp = 3;
/*
Dis dis = 4;
DisTmp disTmp = 5;
WPATmp? wpaTmp = 6;
SecretFileHead? secretFile = 7;
PublicPlat? publicPlat = 8;
TransMsg? transMsg = 9;
AddressListTmp? addressList = 10;
RichStatusTmp? richStatusTmp = 11;
TransCmd? transCmd = 12;
AccostTmp? accostTmp = 13;
PubGroupTmp? pubGroupTmp = 14;
Trans0x211? trans0x211 = 15;
BusinessWPATmp? businessWpaTmp = 16;
AuthTmp? authTmp = 17;
BsnsTmp? bsnsTmp = 18;
QQQueryBusinessTmp? qqQuerybusinessTmp = 19;
NearByDatingTmp? nearbyDatingTmp = 20;
NearByAssistantTmp? nearbyAssistantTmp = 21;
CommTmp? commTmp = 22;
*/
}
message C2C {
int64 toUin = 1;
}
message Grp {
int64 groupCode = 1;
}
message GrpTmp {
int64 groupUin = 1;
int64 toUin = 2;
}
message MsgCtrl {
int32 msgFlag = 1;
}
message GetMessageResponse {
int32 result = 1;
string errorMessage = 2;
bytes syncCookie = 3;
SyncFlag syncFlag = 4;
repeated UinPairMessage uinPairMsgs = 5;
int64 bindUin = 6;
int32 msgRspType = 7;
bytes pubAccountCookie = 8;
bool isPartialSync = 9;
bytes msgCtrlBuf = 10;
}
message PushMessagePacket {
Message message = 1;
int32 svrip = 2;
bytes pushToken = 3;
int32 pingFLag = 4;
int32 generalFlag = 9;
}
message UinPairMessage {
int32 lastReadTime = 1;
int64 peerUin = 2;
int32 msgCompleted = 3;
repeated Message messages = 4;
}
message Message {
MessageHead head = 1;
MessageBody body = 3;
}
message MessageBody {
RichText richText = 1;
bytes msgContent = 2;
bytes msgEncryptContent = 3;
}
message RichText {
Attr attr = 1;
repeated Elem elems = 2;
NotOnlineFile notOnlineFile = 3;
Ptt ptt = 4;
}
message Elem {
Text text = 1;
Face face = 2;
OnlineImage onlineImage = 3;
NotOnlineImage notOnlineImage = 4;
//TransElem transElemInfo = 5;
//MarketFace marketFace = 6;
//ElemFlags elemFlags = 7;
CustomFace customFace = 8;
//ElemFlags2 elemFlags2 = 9;
//FunFace funFace = 10;
//SecretFileMsg secretFile = 11;
//RichMsg richMsg = 12;
GroupFile groupFile = 13;
//PubGroup pubGroup = 14;
//MarketTrans marketTrans = 15;
ExtraInfo extraInfo = 16;
//ShakeWindow? shakeWindow = 17;
//PubAccount? pubAccount = 18;
VideoFile videoFile = 19;
//TipsInfo? tipsInfo = 20;
AnonymousGroupMessage anonGroupMsg = 21;
//QQLiveOld? qqLiveOld = 22;
//LifeOnlineAccount? lifeOnline = 23;
//QQWalletMsg? qqwalletMsg = 24;
//CrmElem? crmElem = 25;
//ConferenceTipsInfo? conferenceTipsInfo = 26;
//RedBagInfo? redbagInfo = 27;
//LowVersionTips? lowVersionTips = 28;
//bytes bankcodeCtrlInfo = 29;
//NearByMessageType? nearByMsg = 30;
CustomElem customElem = 31;
//LocationInfo? locationInfo = 32;
//PubAccInfo? pubAccInfo = 33;
//SmallEmoji? smallEmoji = 34;
//FSJMessageElem? fsjMsgElem = 35;
//ArkAppElem? arkApp = 36;
//GeneralFlags? generalFlags = 37;
//CustomFace? hcFlashPic = 38;
//DeliverGiftMsg? deliverGiftMsg = 39;
//BitAppMsg? bitappMsg = 40;
//OpenQQData? openQqData = 41;
//ApolloActMsg? apolloMsg = 42;
//GroupPubAccountInfo? groupPubAccInfo = 43;
//BlessingMessage? blessMsg = 44;
//SourceMsg? srcMsg = 45;
//LolaMsg? lolaMsg = 46;
//GroupBusinessMsg? groupBusinessMsg = 47;
//WorkflowNotifyMsg? msgWorkflowNotify = 48;
//PatsElem? patElem = 49;
//GroupPostElem? groupPostElem = 50;
//LightAppElem? lightApp = 51;
//EIMInfo? eimInfo = 52;
//CommonElem? commonElem = 53;
}
message CustomElem {
bytes desc = 1;
bytes data = 2;
int32 enumType = 3;
bytes ext = 4;
bytes sound = 5;
}
message Text {
string str = 1;
string link = 2;
bytes attr6Buf = 3;
bytes attr7Buf = 4;
bytes buf = 11;
bytes pbReserve = 12;
}
message Attr {
int32 codePage = 1;
int32 time = 2;
int32 random = 3;
int32 color = 4;
int32 size = 5;
int32 effect = 6;
int32 charSet = 7;
int32 pitchAndFamily = 8;
string fontName = 9;
bytes reserveData = 10;
}
message Ptt {
int32 fileType = 1;
int64 srcUin = 2;
bytes fileUuid = 3;
bytes fileMd5 = 4;
bytes fileName = 5;
int32 fileSize = 6;
bytes reserve = 7;
int32 fileId = 8;
int32 serverIp = 9;
int32 serverPort = 10;
bool boolValid = 11;
bytes signature = 12;
bytes shortcut = 13;
bytes fileKey = 14;
int32 magicPttIndex = 15;
int32 voiceSwitch = 16;
bytes pttUrl = 17;
bytes groupFileKey = 18;
int32 time = 19;
bytes downPara = 20;
int32 format = 29;
bytes pbReserve = 30;
repeated bytes bytesPttUrls = 31;
int32 downloadFlag = 32;
}
message OnlineImage {
bytes guid = 1;
bytes filePath = 2;
bytes oldVerSendFile = 3;
}
message NotOnlineImage {
string filePath = 1;
int32 fileLen = 2;
string downloadPath = 3;
bytes oldVerSendFile = 4;
int32 imgType = 5;
bytes previewsImage = 6;
bytes picMd5 = 7;
int32 picHeight = 8;
int32 picWidth = 9;
string resId = 10;
bytes flag = 11;
string thumbUrl = 12;
int32 original = 13;
string bigUrl = 14;
string origUrl = 15;
int32 bizType = 16;
int32 result = 17;
int32 index = 18;
bytes opFaceBuf = 19;
bool oldPicMd5 = 20;
int32 thumbWidth = 21;
int32 thumbHeight = 22;
int32 fileId = 23;
int32 showLen = 24;
int32 downloadLen = 25;
bytes pbReserve = 29;
}
message NotOnlineFile {
int32 fileType = 1;
bytes sig = 2;
bytes fileUuid = 3;
bytes fileMd5 = 4;
bytes fileName = 5;
int64 fileSize = 6;
bytes note = 7;
int32 reserved = 8;
int32 subcmd = 9;
int32 microCloud = 10;
repeated bytes bytesFileUrls = 11;
int32 downloadFlag = 12;
int32 dangerEvel = 50;
int32 lifeTime = 51;
int32 uploadTime = 52;
int32 absFileType = 53;
int32 clientType = 54;
int32 expireTime = 55;
bytes pbReserve = 56;
}
message ExtraInfo {
bytes nick = 1;
bytes groupCard = 2;
int32 level = 3;
int32 flags = 4;
int32 groupMask = 5;
int32 msgTailId = 6;
bytes senderTitle = 7;
bytes apnsTips = 8;
int64 uin = 9;
int32 msgStateFlag = 10;
int32 apnsSoundType = 11;
int32 newGroupFlag = 12;
}
message GroupFile {
bytes filename = 1;
int64 fileSize = 2;
bytes fileId = 3;
bytes batchId = 4;
bytes fileKey = 5;
bytes mark = 6;
int64 sequence = 7;
bytes batchItemId = 8;
int32 feedMsgTime = 9;
bytes pbReserve = 10;
}
message AnonymousGroupMessage {
int32 flags = 1;
bytes anonId = 2;
bytes anonNick = 3;
int32 headPortrait = 4;
int32 expireTime = 5;
int32 bubbleId = 6;
bytes rankColor = 7;
}
message VideoFile {
bytes fileUuid = 1;
bytes fileMd5 = 2;
bytes fileName = 3;
int32 fileFormat = 4;
int32 fileTime = 5;
int32 fileSize = 6;
int32 thumbWidth = 7;
int32 thumbHeight = 8;
bytes thumbFileMd5 = 9;
bytes source = 10;
int32 thumbFileSize = 11;
int32 busiType = 12;
int32 fromChatType = 13;
int32 toChatType = 14;
bool boolSupportProgressive = 15;
int32 fileWidth = 16;
int32 fileHeight = 17;
int32 subBusiType = 18;
int32 videoAttr = 19;
repeated bytes bytesThumbFileUrls = 20;
repeated bytes bytesVideoFileUrls = 21;
int32 thumbDownloadFlag = 22;
int32 videoDownloadFlag = 23;
bytes pbReserve = 24;
}
message Face {
int32 index = 1;
bytes old = 2;
bytes buf = 11;
}
message CustomFace {
bytes guid = 1;
string filePath = 2;
string shortcut = 3;
bytes buffer = 4;
bytes flag = 5;
bytes oldData = 6;
int32 fileId = 7;
int32 serverIp = 8;
int32 serverPort = 9;
int32 fileType = 10;
bytes signature = 11;
int32 useful = 12;
bytes md5 = 13;
string thumbUrl = 14;
string bigUrl = 15;
string origUrl = 16;
int32 bizType = 17;
int32 repeatIndex = 18;
int32 repeatImage = 19;
int32 imageType = 20;
int32 index = 21;
int32 width = 22;
int32 height = 23;
int32 source = 24;
int32 size = 25;
int32 origin = 26;
int32 thumbWidth = 27;
int32 thumbHeight = 28;
int32 showLen = 29;
int32 downloadLen = 30;
string _400Url = 31;
int32 _400Width = 32;
int32 _400Height = 33;
bytes pbReserve = 34;
}
message ContentHead {
int32 pkgNum = 1;
int32 pkgIndex = 2;
int32 divSeq = 3;
int32 autoReply = 4;
}
message MessageHead {
int64 fromUin = 1;
int64 toUin = 2;
int32 msgType = 3;
int32 c2cCmd = 4;
int32 msgSeq = 5;
int32 msgTime = 6;
int64 msgUid = 7;
C2CTempMessageHead c2cTmpMsgHead = 8;
GroupInfo groupInfo = 9;
int32 fromAppid = 10;
int32 fromInstid = 11;
int32 userActive = 12;
DiscussInfo discussInfo = 13;
string fromNick = 14;
int64 authUin = 15;
string authNick = 16;
int32 msgFlag = 17;
string authRemark = 18;
string groupName = 19;
MutilTransHead mutiltransHead = 20;
InstCtrl msgInstCtrl = 21;
int32 publicAccountGroupSendFlag = 22;
int32 wseqInC2cMsghead = 23;
int64 cpid = 24;
ExtGroupKeyInfo extGroupKeyInfo = 25;
string multiCompatibleText = 26;
int32 authSex = 27;
bool isSrcMsg = 28;
}
message GroupInfo {
int64 groupCode = 1;
int32 groupType = 2;
int64 groupInfoSeq = 3;
string groupCard = 4;
bytes groupRank = 5;
int32 groupLevel = 6;
int32 groupCardType = 7;
bytes groupName = 8;
}
message DiscussInfo {
int64 discussUin = 1;
int32 discussType = 2;
int64 discussInfoSeq = 3;
bytes discussRemark = 4;
bytes discussName = 5;
}
message MutilTransHead{
int32 status = 1;
int32 msgId = 2;
}
message C2CTempMessageHead {
int32 c2cType = 1;
int32 serviceType = 2;
int64 groupUin = 3;
int64 groupCode = 4;
bytes sig = 5;
int32 sigType = 6;
string fromPhone = 7;
string toPhone = 8;
int32 lockDisplay = 9;
int32 directionFlag = 10;
bytes reserved = 11;
}
message InstCtrl {
repeated InstInfo msgSendToInst = 1;
repeated InstInfo msgExcludeInst = 2;
InstInfo msgFromInst = 3;
}
message InstInfo {
int32 apppid = 1;
int32 instid = 2;
int32 platform = 3;
int32 enumDeviceType = 10;
}
message ExtGroupKeyInfo {
int32 curMaxSeq = 1;
int64 curTime = 2;
}
message SyncCookie {
int64 time1 = 1;
int64 time = 2;
int64 ran1 = 3;
int64 ran2 = 4;
int64 const1 = 5;
int64 const2 = 11;
int64 const3 = 12;
int64 lastSyncTime = 13;
int64 const4 = 14;
}
enum SyncFlag {
START = 0;
CONTINUME = 1;
STOP = 2;
}

153
client/tlv_decoders.go Normal file
View File

@ -0,0 +1,153 @@
package client
import (
"fmt"
"github.com/Mrs4s/MiraiGo/binary"
"time"
)
// --- tlv decoders for qq client ---
func (c *QQClient) decodeT161(data []byte) {
reader := binary.NewReader(data)
reader.ReadBytes(2)
t := reader.ReadTlvMap(2)
if t172, ok := t[0x172]; ok {
c.rollbackSig = t172
}
}
func (c *QQClient) decodeT119(data []byte) {
tea := binary.NewTeaCipher(SystemDeviceInfo.TgtgtKey)
reader := binary.NewReader(tea.Decrypt(data))
reader.ReadBytes(2)
m := reader.ReadTlvMap(2)
if t130, ok := m[0x130]; ok {
c.decodeT130(t130)
}
if t113, ok := m[0x113]; ok {
c.decodeT113(t113)
}
if t528, ok := m[0x528]; ok {
c.t528 = t528
}
if t530, ok := m[0x530]; ok {
c.t530 = t530
}
if t108, ok := m[0x108]; ok {
c.ksid = t108
}
var (
//openId []byte
//openKey []byte
//payToken []byte
//pf []byte
//pfkey []byte
gender uint16 = 0
age uint16 = 0
nick = ""
//a1 []byte
//noPicSig []byte
//ctime = time.Now().Unix()
//etime = ctime + 2160000
)
if _, ok := m[0x125]; ok {
//openId, openKey = readT125(t125)
}
if t186, ok := m[0x186]; ok {
c.decodeT186(t186)
}
if t11a, ok := m[0x11a]; ok {
nick, age, gender = readT11A(t11a)
}
if _, ok := m[0x199]; ok {
//openId, payToken = readT199(t199)
}
if _, ok := m[0x200]; ok {
//pf, pfkey = readT200(t200)
}
if _, ok := m[0x512]; ok {
} // 暂不处理, Http api cookie
if _, ok := m[0x531]; ok {
//a1, noPicSig = readT531(t531)
}
c.sigInfo = &loginSigInfo{
loginBitmap: 0,
tgt: m[0x10a],
tgtKey: m[0x10d],
userStKey: m[0x10e],
userStWebSig: m[0x103],
sKey: m[0x120],
d2: m[0x143],
d2Key: m[0x305],
wtSessionTicketKey: m[0x134],
deviceToken: m[0x322],
}
c.Nickname = nick
c.Age = age
c.Gender = gender
}
func (c *QQClient) decodeT130(data []byte) {
reader := binary.NewReader(data)
reader.ReadBytes(2)
c.timeDiff = int64(reader.ReadInt32()) - time.Now().Unix()
c.t149 = reader.ReadBytes(4)
}
func (c *QQClient) decodeT113(data []byte) {
reader := binary.NewReader(data)
uin := reader.ReadInt32() // ?
fmt.Println("got t113 uin:", uin)
}
func (c *QQClient) decodeT186(data []byte) {
c.pwdFlag = data[1] == 1
}
// --- tlv readers ---
func readT125(data []byte) (openId, openKey []byte) {
reader := binary.NewReader(data)
openId = reader.ReadBytesShort()
openKey = reader.ReadBytesShort()
return
}
func readT11A(data []byte) (nick string, age, gender uint16) {
reader := binary.NewReader(data)
reader.ReadUInt16()
age = reader.ReadUInt16()
gender = uint16(reader.ReadByte())
nick = reader.ReadStringLimit(int(reader.ReadByte()) & 0xff)
return
}
func readT199(data []byte) (openId, payToken []byte) {
reader := binary.NewReader(data)
openId = reader.ReadBytesShort()
payToken = reader.ReadBytesShort()
return
}
func readT200(data []byte) (pf, pfKey []byte) {
reader := binary.NewReader(data)
pf = reader.ReadBytesShort()
pfKey = reader.ReadBytesShort()
return
}
func readT531(data []byte) (a1, noPicSig []byte) {
reader := binary.NewReader(data)
m := reader.ReadTlvMap(2)
if m.Exists(0x103) && m.Exists(0x16a) && m.Exists(0x113) && m.Exists(0x10c) {
a1 = append(m[0x106], m[0x10c]...)
noPicSig = m[0x16a]
}
return
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/Mrs4s/MiraiGo
go 1.14
require (
github.com/golang/protobuf v1.4.1
google.golang.org/protobuf v1.25.0
)

85
go.sum Normal file
View File

@ -0,0 +1,85 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/ishbir/elliptic v0.0.0-20150227224012-7fa75501baae h1:p4rT8RR3B5Q6rXTG5v0Tns45zn4wJucqQKF5hzrjtgU=
github.com/ishbir/elliptic v0.0.0-20150227224012-7fa75501baae/go.mod h1:JPqapJUq3G3ojsWKJHZNsUK2OJmarnOxFN9afp3/Ovo=
github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8=
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

227
message/elements.go Normal file
View File

@ -0,0 +1,227 @@
package message
import "strings"
type TextElement struct {
Content string
}
type ImageElement struct {
Filename string
Url string
Data []byte
}
type GroupImageElement struct {
ImageId string
Md5 []byte
Url string
}
type FaceElement struct {
Index int32
Name string
}
func NewText(s string) *TextElement {
return &TextElement{Content: s}
}
func NewNetImage(filename, url string) *ImageElement {
return &ImageElement{
Filename: filename,
Url: url,
}
}
func NewGroupImage(id string, md5 []byte) *GroupImageElement {
return &GroupImageElement{
ImageId: id,
Md5: md5,
Url: "http://gchat.qpic.cn/gchatpic_new/1/0-0-" + strings.ReplaceAll(id[1:36], "-", "") + "/0?term=2",
}
}
func NewFace(index int32) *FaceElement {
name := faceMap[int(index)]
if name == "" {
name = "未知表情"
}
return &FaceElement{
Index: index,
Name: name,
}
}
func (e *TextElement) Type() ElementType {
return Text
}
func (e *ImageElement) Type() ElementType {
return Image
}
func (e *FaceElement) Type() ElementType {
return Face
}
func (e *GroupImageElement) Type() ElementType {
return Image
}
var faceMap = map[int]string{
14: "微笑",
1: "撇嘴",
2: "色",
3: "发呆",
4: "得意",
5: "流泪",
6: "害羞",
7: "闭嘴",
8: "睡",
9: "大哭",
10: "尴尬",
11: "发怒",
12: "调皮",
13: "呲牙",
0: "惊讶",
15: "难过",
16: "酷",
96: "冷汗",
18: "抓狂",
19: "吐",
20: "偷笑",
21: "可爱",
22: "白眼",
23: "傲慢",
24: "饥饿",
25: "困",
26: "惊恐",
27: "流汗",
28: "憨笑",
29: "大兵",
30: "奋斗",
31: "咒骂",
32: "疑问",
33: "嘘",
34: "晕",
35: "折磨",
36: "衰",
37: "骷髅",
38: "敲打",
39: "再见",
97: "擦汗",
98: "抠鼻",
99: "鼓掌",
100: "糗大了",
101: "坏笑",
102: "左哼哼",
103: "右哼哼",
104: "哈欠",
105: "鄙视",
106: "委屈",
107: "快哭了",
108: "阴险",
109: "亲亲",
110: "吓",
111: "可怜",
172: "眨眼睛",
182: "笑哭",
179: "doge",
173: "泪奔",
174: "无奈",
212: "托腮",
175: "卖萌",
178: "斜眼笑",
177: "喷血",
180: "惊喜",
181: "骚扰",
176: "小纠结",
183: "我最美",
112: "菜刀",
89: "西瓜",
113: "啤酒",
114: "篮球",
115: "乒乓",
171: "茶",
60: "咖啡",
61: "饭",
46: "猪头",
63: "玫瑰",
64: "凋谢",
116: "示爱",
66: "爱心",
67: "心碎",
53: "蛋糕",
54: "闪电",
55: "炸弹",
56: "刀",
57: "足球",
117: "瓢虫",
59: "便便",
75: "月亮",
74: "太阳",
69: "礼物",
49: "拥抱",
76: "强",
77: "弱",
78: "握手",
79: "胜利",
118: "抱拳",
119: "勾引",
120: "拳头",
121: "差劲",
122: "爱你",
123: "NO",
124: "OK",
42: "爱情",
85: "飞吻",
43: "跳跳",
41: "发抖",
86: "怄火",
125: "转圈",
126: "磕头",
127: "回头",
128: "跳绳",
129: "挥手",
130: "激动",
131: "街舞",
132: "献吻",
133: "左太极",
134: "右太极",
136: "双喜",
137: "鞭炮",
138: "灯笼",
140: "K歌",
144: "喝彩",
145: "祈祷",
146: "爆筋",
147: "棒棒糖",
148: "喝奶",
151: "飞机",
158: "钞票",
168: "药",
169: "手枪",
188: "蛋",
192: "红包",
184: "河蟹",
185: "羊驼",
190: "菊花",
187: "幽灵",
193: "大笑",
194: "不开心",
197: "冷漠",
198: "呃",
199: "好棒",
200: "拜托",
201: "点赞",
202: "无聊",
203: "托脸",
204: "吃",
205: "送花",
206: "害怕",
207: "花痴",
208: "小样儿",
210: "飙泪",
211: "我不看",
}

116
message/message.go Normal file
View File

@ -0,0 +1,116 @@
package message
import (
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
)
type PrivateMessage struct {
Id int32
Sender *Sender
Elements []IMessageElement
}
type GroupMessage struct {
Id int32
GroupUin int64
GroupName string
Sender *Sender
Elements []IMessageElement
IsAdministrator bool
}
type SendingMessage struct {
Elements []IMessageElement
}
type Sender struct {
Uin int64
Nickname string
CardName string
IsFriend bool
}
type IMessageElement interface {
Type() ElementType
}
type ElementType int
const (
Text ElementType = iota
Image
Face
)
func (s *Sender) IsAnonymous() bool {
return s.Uin == 80000000
}
func (msg *PrivateMessage) ToString() (res string) {
for _, elem := range msg.Elements {
switch e := elem.(type) {
case *TextElement:
res += e.Content
case *ImageElement:
res += " [Image= " + e.Filename + " ] "
case *FaceElement:
res += " [" + e.Name + "] "
}
}
return
}
func (msg *GroupMessage) ToString() (res string) {
for _, elem := range msg.Elements {
switch e := elem.(type) {
case *TextElement:
res += e.Content
case *ImageElement:
res += " [Image= " + e.Filename + " ] "
case *FaceElement:
res += " [" + e.Name + "] "
case *GroupImageElement:
res += "[Image= " + e.ImageId + " ]"
}
}
return
}
func (msg *SendingMessage) Append(e IMessageElement) *SendingMessage {
msg.Elements = append(msg.Elements, e)
return msg
}
func ToProtoElems(elems []IMessageElement) (r []*msg.Elem) {
for _, elem := range elems {
switch e := elem.(type) {
case *TextElement:
r = append(r, &msg.Elem{
Text: &msg.Text{
Str: e.Content,
},
})
case *FaceElement:
r = append(r, &msg.Elem{
Face: &msg.Face{
Index: e.Index,
Old: binary.ToBytes(int16(0x1445 - 4 + e.Index)),
Buf: []byte{0x00, 0x01, 0x00, 0x04, 0x52, 0xCC, 0xF5, 0xD0},
},
})
case *GroupImageElement:
r = append(r, &msg.Elem{
CustomFace: &msg.CustomFace{
FilePath: e.ImageId,
Md5: e.Md5[:],
Flag: make([]byte, 4),
OldData: []byte{0x15, 0x36, 0x20, 0x39, 0x32, 0x6B, 0x41, 0x31, 0x00, 0x38, 0x37, 0x32, 0x66, 0x30, 0x36, 0x36, 0x30, 0x33, 0x61, 0x65, 0x31, 0x30, 0x33, 0x62, 0x37, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x35, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7B, 0x30, 0x31, 0x45, 0x39, 0x34, 0x35, 0x31, 0x42, 0x2D, 0x37, 0x30, 0x45, 0x44,
0x2D, 0x45, 0x41, 0x45, 0x33, 0x2D, 0x42, 0x33, 0x37, 0x43, 0x2D, 0x31, 0x30, 0x31, 0x46, 0x31, 0x45, 0x45, 0x42, 0x46, 0x35, 0x42, 0x35, 0x7D, 0x2E, 0x70, 0x6E, 0x67, 0x41},
},
})
}
}
return
}

35
protocol/crypto/crypto.go Normal file
View File

@ -0,0 +1,35 @@
package crypto
import (
"encoding/hex"
"github.com/Mrs4s/MiraiGo/binary"
)
type EncryptECDH struct {
InitialShareKey []byte
PublicKey []byte
}
var ECDH = &EncryptECDH{}
func init() {
//TODO: Keygen
ECDH.InitialShareKey, _ = hex.DecodeString("41d0d17c506a5256d0d08d7aac133c70")
ECDH.PublicKey, _ = hex.DecodeString("049fb03421ba7ab5fc91c2d94a7657fff7ba8fe09f08a22951a24865212cbc45aff1b5125188fa8f0e30473bc55d54edc2")
}
func (e *EncryptECDH) DoEncrypt(d, k []byte) []byte {
w := binary.NewWriter()
w.WriteByte(0x01)
w.WriteByte(0x01)
w.Write(k)
w.WriteUInt16(258)
w.WriteUInt16(uint16(len(ECDH.PublicKey)))
w.Write(ECDH.PublicKey)
w.EncryptAndWrite(ECDH.InitialShareKey, d)
return w.Bytes()
}
func (e *EncryptECDH) Id() byte {
return 7
}

1
protocol/crypto/ecdh.go Normal file
View File

@ -0,0 +1 @@
package crypto

View File

@ -0,0 +1,233 @@
package crypto
import (
"io"
"math/big"
)
// A BitCurve represents a Koblitz Curve with a=0.
// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html
type BitCurve struct {
P *big.Int // the order of the underlying field
N *big.Int // the order of the base point
B *big.Int // the constant of the BitCurve equation
Gx, Gy *big.Int // (x,y) of the base point
BitSize int // the size of the underlying field
}
// See FIPS 186-3, section D.2.2.1
// And http://www.secg.org/sec2-v2.pdf section 2.2.1
var secp192k1 = &BitCurve{
P: new(big.Int).SetBytes([]byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xEE, 0x37,
}), N: new(big.Int).SetBytes([]byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0x26, 0xF2, 0xFC, 0x17, 0x0F, 0x69, 0x46, 0x6A, 0x74, 0xDE, 0xFD, 0x8D,
}), B: new(big.Int).SetBytes([]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
}), Gx: new(big.Int).SetBytes([]byte{
0xDB, 0x4F, 0xF1, 0x0E, 0xC0, 0x57, 0xE9, 0xAE, 0x26, 0xB0, 0x7D, 0x02,
0x80, 0xB7, 0xF4, 0x34, 0x1D, 0xA5, 0xD1, 0xB1, 0xEA, 0xE0, 0x6C, 0x7D,
}), Gy: new(big.Int).SetBytes([]byte{
0x9B, 0x2F, 0x2F, 0x6D, 0x9C, 0x56, 0x28, 0xA7, 0x84, 0x41, 0x63, 0xD0,
0x15, 0xBE, 0x86, 0x34, 0x40, 0x82, 0xAA, 0x88, 0xD9, 0x5E, 0x2F, 0x9D,
}),
BitSize: 192,
}
func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
zinv := new(big.Int).ModInverse(z, BitCurve.P)
zinvsq := new(big.Int).Mul(zinv, zinv)
xOut = new(big.Int).Mul(x, zinvsq)
xOut.Mod(xOut, BitCurve.P)
zinvsq.Mul(zinvsq, zinv)
yOut = new(big.Int).Mul(y, zinvsq)
yOut.Mod(yOut, BitCurve.P)
return
}
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
// (x2, y2, z2) and returns their sum, also in Jacobian form.
func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
z1z1 := new(big.Int).Mul(z1, z1)
z1z1.Mod(z1z1, BitCurve.P)
z2z2 := new(big.Int).Mul(z2, z2)
z2z2.Mod(z2z2, BitCurve.P)
u1 := new(big.Int).Mul(x1, z2z2)
u1.Mod(u1, BitCurve.P)
u2 := new(big.Int).Mul(x2, z1z1)
u2.Mod(u2, BitCurve.P)
h := new(big.Int).Sub(u2, u1)
if h.Sign() == -1 {
h.Add(h, BitCurve.P)
}
i := new(big.Int).Lsh(h, 1)
i.Mul(i, i)
j := new(big.Int).Mul(h, i)
s1 := new(big.Int).Mul(y1, z2)
s1.Mul(s1, z2z2)
s1.Mod(s1, BitCurve.P)
s2 := new(big.Int).Mul(y2, z1)
s2.Mul(s2, z1z1)
s2.Mod(s2, BitCurve.P)
r := new(big.Int).Sub(s2, s1)
if r.Sign() == -1 {
r.Add(r, BitCurve.P)
}
r.Lsh(r, 1)
v := new(big.Int).Mul(u1, i)
x3 := new(big.Int).Set(r)
x3.Mul(x3, x3)
x3.Sub(x3, j)
x3.Sub(x3, v)
x3.Sub(x3, v)
x3.Mod(x3, BitCurve.P)
y3 := new(big.Int).Set(r)
v.Sub(v, x3)
y3.Mul(y3, v)
s1.Mul(s1, j)
s1.Lsh(s1, 1)
y3.Sub(y3, s1)
y3.Mod(y3, BitCurve.P)
z3 := new(big.Int).Add(z1, z2)
z3.Mul(z3, z3)
z3.Sub(z3, z1z1)
if z3.Sign() == -1 {
z3.Add(z3, BitCurve.P)
}
z3.Sub(z3, z2z2)
if z3.Sign() == -1 {
z3.Add(z3, BitCurve.P)
}
z3.Mul(z3, h)
z3.Mod(z3, BitCurve.P)
return x3, y3, z3
}
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
// returns its double, also in Jacobian form.
func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
a := new(big.Int).Mul(x, x) //X1²
b := new(big.Int).Mul(y, y) //Y1²
c := new(big.Int).Mul(b, b) //B²
d := new(big.Int).Add(x, b) //X1+B
d.Mul(d, d) //(X1+B)²
d.Sub(d, a) //(X1+B)²-A
d.Sub(d, c) //(X1+B)²-A-C
d.Mul(d, big.NewInt(2)) //2*((X1+B)²-A-C)
e := new(big.Int).Mul(big.NewInt(3), a) //3*A
f := new(big.Int).Mul(e, e) //E²
x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
x3.Sub(f, x3) //F-2*D
x3.Mod(x3, BitCurve.P)
y3 := new(big.Int).Sub(d, x3) //D-X3
y3.Mul(e, y3) //E*(D-X3)
y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
y3.Mod(y3, BitCurve.P)
z3 := new(big.Int).Mul(y, z) //Y1*Z1
z3.Mul(big.NewInt(2), z3) //3*Y1*Z1
z3.Mod(z3, BitCurve.P)
return x3, y3, z3
}
//TODO: double check if it is okay
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
// We have a slight problem in that the identity of the group (the
// point at infinity) cannot be represented in (x, y) form on a finite
// machine. Thus the standard add/double algorithm has to be tweaked
// slightly: our initial state is not the identity, but x, and we
// ignore the first true bit in |k|. If we don't find any true bits in
// |k|, then we return nil, nil, because we cannot return the identity
// element.
Bz := new(big.Int).SetInt64(1)
x := Bx
y := By
z := Bz
seenFirstTrue := false
for _, byte := range k {
for bitNum := 0; bitNum < 8; bitNum++ {
if seenFirstTrue {
x, y, z = BitCurve.doubleJacobian(x, y, z)
}
if byte&0x80 == 0x80 {
if !seenFirstTrue {
seenFirstTrue = true
} else {
x, y, z = BitCurve.addJacobian(Bx, By, Bz, x, y, z)
}
}
byte <<= 1
}
}
if !seenFirstTrue {
return nil, nil
}
return BitCurve.affineFromJacobian(x, y, z)
}
// ScalarBaseMult returns k*G, where G is the base point of the group and k is
// an integer in big-endian form.
func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
return BitCurve.ScalarMult(BitCurve.Gx, BitCurve.Gy, k)
}
var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f}
//TODO: double check if it is okay
// GenerateKey returns a public/private key pair. The private key is generated
// using the given reader, which must return random data.
func (BitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) {
byteLen := (BitCurve.BitSize + 7) >> 3
priv = make([]byte, byteLen)
for x == nil {
_, err = io.ReadFull(rand, priv)
if err != nil {
return
}
// We have to mask off any excess bits in the case that the size of the
// underlying field is not a whole number of bytes.
priv[0] &= mask[BitCurve.BitSize%8]
// This is because, in tests, rand will return all zeros and we don't
// want to get the point at infinity and loop forever.
priv[1] ^= 0x42
x, y = BitCurve.ScalarBaseMult(priv)
}
return
}
/*
$ openssl asn1parse -in 1.cer -inform DER -dump
0:d=0 hl=2 l= 70 cons: SEQUENCE
2:d=1 hl=2 l= 16 cons: SEQUENCE
4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
13:d=2 hl=2 l= 5 prim: OBJECT :secp192k1
20:d=1 hl=2 l= 50 prim: BIT STRING
0000 - 00 04 92 8d 88 50 67 30-88 b3 43 26 4e 0c 6b ac .....Pg0..C&N.k.
0010 - b8 49 6d 69 77 99 f3 72-11 de b2 5b b7 39 06 cb .Imiw..r...[.9..
0020 - 08 9f ea 96 39 b4 e0 26-04 98 b5 1a 99 2d 50 81 ....9..&.....-P.
0030 - 3d a8 =.
*/

View File

@ -0,0 +1,40 @@
package packets
import (
"github.com/Mrs4s/MiraiGo/binary"
"strconv"
)
func BuildLoginPacket(uin int64, bodyType byte, key, body, extraData []byte) []byte {
w := binary.NewWriter()
w.WriteIntLvPacket(4, func(w *binary.Writer) {
w.WriteUInt32(0x00_00_00_0A)
w.WriteByte(bodyType)
w.WriteIntLvPacket(4, func(w *binary.Writer) {
w.Write(extraData)
})
w.WriteByte(0x00)
w.WriteString(strconv.FormatInt(uin, 10))
if len(key) == 0 {
w.Write(body)
} else {
w.EncryptAndWrite(key, body)
}
})
return w.Bytes()
}
func BuildUniPacket(uin int64, seq uint16, commandName string, bodyType byte, sessionId, extraData, key, body []byte) []byte {
w := binary.NewWriter()
w.WriteIntLvPacket(4, func(w *binary.Writer) {
w.WriteUInt32(0x0B)
w.WriteByte(bodyType)
w.WriteUInt32(uint32(seq))
w.WriteByte(0)
w.WriteString(strconv.FormatInt(uin, 10))
w.EncryptAndWrite(key, binary.NewWriterF(func(w *binary.Writer) {
w.WriteUniPacket(commandName, sessionId, extraData, body)
}))
})
return w.Bytes()
}

195
protocol/packets/global.go Normal file
View File

@ -0,0 +1,195 @@
package packets
import (
"errors"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/protocol/crypto"
)
var ErrUnknownFlag = errors.New("unknown flag")
var ErrDecryptFailed = errors.New("decrypt failed")
type ISendingPacket interface {
CommandId() uint16
Writer() *binary.Writer
}
type IncomingPacket struct {
SequenceId uint16
Flag2 byte
CommandName string
SessionId []byte
Payload []byte
}
type IEncryptMethod interface {
DoEncrypt([]byte, []byte) []byte
Id() byte
}
func BuildOicqRequestPacket(uin int64, commandId uint16, encrypt IEncryptMethod, key []byte, bodyFunc func(writer *binary.Writer)) []byte {
b := binary.NewWriter()
bodyFunc(b)
body := encrypt.DoEncrypt(b.Bytes(), key)
p := binary.NewWriter()
p.WriteByte(0x02)
p.WriteUInt16(27 + 2 + uint16(len(body)))
p.WriteUInt16(8001)
p.WriteUInt16(commandId)
p.WriteUInt16(1)
p.WriteUInt32(uint32(uin))
p.WriteByte(3)
p.WriteByte(encrypt.Id())
p.WriteByte(0)
p.WriteUInt32(2)
p.WriteUInt32(0)
p.WriteUInt32(0)
p.Write(body)
p.WriteByte(0x03)
return p.Bytes()
}
func BuildSsoPacket(seq uint16, commandName, imei string, extData, outPacketSessionId, body, ksid []byte) []byte {
p := binary.NewWriter()
p.WriteIntLvPacket(4, func(writer *binary.Writer) {
writer.WriteUInt32(uint32(seq))
writer.WriteUInt32(537062409) // Android pad (sub app id)
writer.WriteUInt32(537062409)
writer.Write([]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
if len(extData) == 0 || len(extData) == 4 {
writer.WriteUInt32(0x04)
} else {
writer.WriteUInt32(uint32(len(extData) + 4))
writer.Write(extData)
}
writer.WriteString(commandName)
writer.WriteUInt32(0x08)
writer.Write(outPacketSessionId)
writer.WriteString(imei)
writer.WriteUInt32(0x04)
{
writer.WriteUInt16(uint16(len(ksid)) + 2)
writer.Write(ksid)
}
writer.WriteUInt32(0x04)
})
p.WriteIntLvPacket(4, func(writer *binary.Writer) {
writer.Write(body)
})
return p.Bytes()
}
func ParseIncomingPacket(payload, d2key []byte) (*IncomingPacket, error) {
reader := binary.NewReader(payload)
flag1 := reader.ReadInt32()
flag2 := reader.ReadByte()
if reader.ReadByte() != 0 { // flag3
return nil, ErrUnknownFlag
}
reader.ReadString() // uin string
decrypted := func() (data []byte) {
defer func() {
if pan := recover(); pan != nil {
// TODO: bot.client.tryDecryptOrNull
}
}()
switch flag2 {
case 0:
return reader.ReadAvailable()
case 1:
d2 := binary.NewTeaCipher(d2key)
return d2.Decrypt(reader.ReadAvailable())
case 2:
z16 := binary.NewTeaCipher(make([]byte, 16))
return z16.Decrypt(reader.ReadAvailable())
}
return nil
}()
if len(decrypted) == 0 {
return nil, ErrDecryptFailed
}
if flag1 != 0x0A && flag1 != 0x0B {
return nil, ErrDecryptFailed
}
return parseSsoFrame(decrypted, flag2), nil
}
func parseSsoFrame(payload []byte, flag2 byte) *IncomingPacket {
reader := binary.NewReader(payload)
reader.ReadInt32() // packet len
seqId := reader.ReadInt32()
reader.ReadInt32() // return code
reader.ReadBytes(int(reader.ReadInt32()) - 4) // extra data
commandName := reader.ReadString()
sessionId := reader.ReadBytes(int(reader.ReadInt32()) - 4)
if commandName == "Heartbeat.Alive" {
return &IncomingPacket{
SequenceId: uint16(seqId),
Flag2: flag2,
CommandName: commandName,
SessionId: sessionId,
Payload: []byte{},
}
}
compressedFlag := reader.ReadInt32()
packet := func() []byte {
if compressedFlag == 0 {
pktSize := uint64(reader.ReadInt32()) & 0xffffffff
if pktSize == uint64(reader.Len()) || pktSize == uint64(reader.Len()+4) {
return reader.ReadAvailable()
} else {
return reader.ReadAvailable() // some logic
}
}
if compressedFlag == 1 {
reader.ReadBytes(4)
return binary.ZlibUncompress(reader.ReadAvailable()) // ?
}
if compressedFlag == 8 {
return reader.ReadAvailable()
}
return nil
}()
return &IncomingPacket{
SequenceId: uint16(seqId),
Flag2: flag2,
CommandName: commandName,
SessionId: sessionId,
Payload: packet,
}
}
func (pkt *IncomingPacket) DecryptPayload(random []byte) ([]byte, error) {
reader := binary.NewReader(pkt.Payload)
if reader.ReadByte() != 2 {
return nil, ErrUnknownFlag
}
reader.ReadBytes(2)
reader.ReadBytes(2)
reader.ReadUInt16()
reader.ReadUInt16()
reader.ReadInt32()
encryptType := reader.ReadUInt16()
reader.ReadByte()
if encryptType == 0 {
data := func() (decrypted []byte) {
d := reader.ReadBytes(reader.Len() - 1)
defer func() {
if pan := recover(); pan != nil {
tea := binary.NewTeaCipher(random)
decrypted = tea.Decrypt(d)
}
}()
tea := binary.NewTeaCipher(crypto.ECDH.InitialShareKey)
decrypted = tea.Decrypt(d)
return
}()
return data, nil
}
if encryptType == 4 {
panic("todo")
}
return nil, ErrUnknownFlag
}

View File

@ -0,0 +1 @@
package protocol

24
protocol/tlv/t1.go Normal file
View File

@ -0,0 +1,24 @@
package tlv
import (
"github.com/Mrs4s/MiraiGo/binary"
"math/rand"
"time"
)
func T1(uin uint32, ip []byte) []byte {
if len(ip) != 4 {
panic("invalid ip")
}
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x01)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(1)
w.WriteUInt32(rand.Uint32())
w.WriteUInt32(uin)
w.WriteUInt32(uint32(time.Now().UnixNano() / 1e6))
w.Write(ip)
w.WriteUInt16(0)
}))
})
}

17
protocol/tlv/t100.go Normal file
View File

@ -0,0 +1,17 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T100() []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x100)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(1)
w.WriteUInt32(5)
w.WriteUInt32(16)
w.WriteUInt32(537062409) // Sub app id
w.WriteUInt32(0) // App client version
w.WriteUInt32(34869472)
}))
})
}

10
protocol/tlv/t104.go Normal file
View File

@ -0,0 +1,10 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T104(data []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x104)
w.WriteTlv(data)
})
}

57
protocol/tlv/t106.go Normal file
View File

@ -0,0 +1,57 @@
package tlv
import (
"crypto/md5"
binary2 "encoding/binary"
"github.com/Mrs4s/MiraiGo/binary"
"math/rand"
"time"
)
func T106(uin, salt uint32, passwordMd5 [16]byte, guidAvailable bool, guid, tgtgtKey []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x106)
body := binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(4)
w.WriteUInt32(rand.Uint32())
w.WriteUInt32(5)
w.WriteUInt32(16) // appId
w.WriteUInt32(0) // app client version
if uin == 0 {
w.WriteUInt64(uint64(salt))
} else {
w.WriteUInt64(uint64(uin))
}
w.WriteUInt32(uint32(time.Now().UnixNano() / 1e6))
w.Write([]byte{0x00, 0x00, 0x00, 0x00}) // fake ip
w.WriteByte(0x01)
w.Write(passwordMd5[:])
w.Write(tgtgtKey)
w.WriteUInt32(0)
w.WriteBool(guidAvailable)
if len(guid) == 0 {
for i := 0; i < 4; i++ {
w.WriteUInt32(rand.Uint32())
}
} else {
w.Write(guid)
}
w.WriteUInt32(537062409) // sub app id (android pad)
w.WriteUInt32(1) // password login
b := make([]byte, 8)
binary2.BigEndian.PutUint64(b, uint64(uin))
w.WriteTlv(b)
w.WriteUInt16(0)
})
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
b := make([]byte, 4)
if salt != 0 {
binary2.BigEndian.PutUint32(b, salt)
} else {
binary2.BigEndian.PutUint32(b, uin)
}
key := md5.Sum(append(append(passwordMd5[:], []byte{0x00, 0x00, 0x00, 0x00}...), b...))
w.EncryptAndWrite(key[:], body)
}))
})
}

15
protocol/tlv/t107.go Normal file
View File

@ -0,0 +1,15 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T107(picType uint16) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x107)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(picType)
w.WriteByte(0x00)
w.WriteUInt16(0)
w.WriteByte(0x01)
}))
})
}

16
protocol/tlv/t109.go Normal file
View File

@ -0,0 +1,16 @@
package tlv
import (
"crypto/md5"
"github.com/Mrs4s/MiraiGo/binary"
)
func T109(androidId []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x109)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
h := md5.Sum(androidId)
w.Write(h[:])
}))
})
}

16
protocol/tlv/t116.go Normal file
View File

@ -0,0 +1,16 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T116(miscBitmap, subSigMap uint32) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x116)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(0x00)
w.WriteUInt32(miscBitmap)
w.WriteUInt32(subSigMap)
w.WriteByte(0x01)
w.WriteUInt32(1600000226) // app id list
}))
})
}

17
protocol/tlv/t124.go Normal file
View File

@ -0,0 +1,17 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T124(osType, osVersion, simInfo, apn []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x124)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteTlvLimitedSize(osType, 16)
w.WriteTlvLimitedSize(osVersion, 16)
w.WriteUInt16(2) // Network type wifi
w.WriteTlvLimitedSize(simInfo, 16)
w.WriteTlvLimitedSize([]byte{}, 16)
w.WriteTlvLimitedSize(apn, 16)
}))
})
}

19
protocol/tlv/t128.go Normal file
View File

@ -0,0 +1,19 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T128(isGuidFromFileNull, isGuidAvailable, isGuidChanged bool, guidFlag uint32, buildModel, guid, buildBrand []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x128)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0)
w.WriteBool(isGuidFromFileNull)
w.WriteBool(isGuidAvailable)
w.WriteBool(isGuidChanged)
w.WriteUInt32(guidFlag)
w.WriteTlvLimitedSize(buildModel, 32)
w.WriteTlvLimitedSize(guid, 16)
w.WriteTlvLimitedSize(buildBrand, 16)
}))
})
}

15
protocol/tlv/t141.go Normal file
View File

@ -0,0 +1,15 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T141(simInfo, apn []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x141)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(1)
w.WriteTlv(simInfo)
w.WriteUInt16(2) // network type wifi
w.WriteTlv(apn)
}))
})
}

13
protocol/tlv/t142.go Normal file
View File

@ -0,0 +1,13 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T142(apkId string) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x142)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0)
w.WriteTlvLimitedSize([]byte(apkId), 32)
}))
})
}

24
protocol/tlv/t144.go Normal file
View File

@ -0,0 +1,24 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T144(
androidId, devInfo, osType, osVersion, simInfo, apn []byte,
isGuidFromFileNull, isGuidAvailable, isGuidChanged bool,
guidFlag uint32,
buildModel, guid, buildBrand, tgtgtKey []byte,
) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x144)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.EncryptAndWrite(tgtgtKey, binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(5)
w.Write(T109(androidId))
w.Write(T52D(devInfo))
w.Write(T124(osType, osVersion, simInfo, apn))
w.Write(T128(isGuidFromFileNull, isGuidAvailable, isGuidChanged, guidFlag, buildModel, guid, buildBrand))
w.Write(T16E(buildModel))
}))
}))
})
}

12
protocol/tlv/t145.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T145(guid []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x145)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.Write(guid)
}))
})
}

14
protocol/tlv/t147.go Normal file
View File

@ -0,0 +1,14 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T147(appId uint32, apkVersionName, apkSignatureMd5 []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x147)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt32(appId)
w.WriteTlvLimitedSize(apkVersionName, 32)
w.WriteTlvLimitedSize(apkSignatureMd5, 32)
}))
})
}

12
protocol/tlv/t154.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T154(seq uint16) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x154)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt32(uint32(seq))
}))
})
}

12
protocol/tlv/t166.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T166(imageType byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x166)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(imageType)
}))
})
}

12
protocol/tlv/t16e.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T16E(buildModel []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x16e)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.Write(buildModel)
}))
})
}

14
protocol/tlv/t177.go Normal file
View File

@ -0,0 +1,14 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T177() []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x177)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(0x01)
w.WriteUInt32(1571193922)
w.WriteTlv([]byte("6.0.0.2413"))
}))
})
}

18
protocol/tlv/t18.go Normal file
View File

@ -0,0 +1,18 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T18(appId uint32, uin uint32) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x18)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(1)
w.WriteUInt32(1536)
w.WriteUInt32(appId)
w.WriteUInt32(0)
w.WriteUInt32(uin)
w.WriteUInt16(0)
w.WriteUInt16(0)
}))
})
}

16
protocol/tlv/t187.go Normal file
View File

@ -0,0 +1,16 @@
package tlv
import (
"crypto/md5"
"github.com/Mrs4s/MiraiGo/binary"
)
func T187(macAddress []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x187)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
h := md5.Sum(macAddress)
w.Write(h[:])
}))
})
}

16
protocol/tlv/t188.go Normal file
View File

@ -0,0 +1,16 @@
package tlv
import (
"crypto/md5"
"github.com/Mrs4s/MiraiGo/binary"
)
func T188(androidId []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x188)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
h := md5.Sum(androidId)
w.Write(h[:])
}))
})
}

12
protocol/tlv/t191.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T191(k byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x191)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(k)
}))
})
}

12
protocol/tlv/t194.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T194(imsiMd5 []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x194)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.Write(imsiMd5)
}))
})
}

14
protocol/tlv/t2.go Normal file
View File

@ -0,0 +1,14 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T2(result string, sign []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x02)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0)
w.WriteStringShort(result)
w.WriteTlv(sign)
}))
})
}

13
protocol/tlv/t202.go Normal file
View File

@ -0,0 +1,13 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T202(wifiBSSID, wifiSSID []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x202)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteTlvLimitedSize(wifiBSSID, 16)
w.WriteTlvLimitedSize(wifiSSID, 32)
}))
})
}

52
protocol/tlv/t511.go Normal file
View File

@ -0,0 +1,52 @@
package tlv
import (
"github.com/Mrs4s/MiraiGo/binary"
"strconv"
"strings"
)
func T511(domains []string) []byte {
var arr2 []string
for _, d := range domains {
if d != "" {
arr2 = append(arr2, d)
}
}
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x511)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(uint16(len(arr2)))
for _, d := range arr2 {
indexOf := strings.Index(d, "(")
indexOf2 := strings.Index(d, ")")
if indexOf != 0 || indexOf2 <= 0 {
w.WriteByte(0x01)
w.WriteTlv([]byte(d))
} else {
var b byte
var z bool
i, err := strconv.Atoi(d[indexOf+1 : indexOf2])
if err == nil {
z2 := (1048576 & i) > 0
if (i & 134217728) > 0 {
z = true
} else {
z = false
}
if z2 {
b = 1
} else {
b = 0
}
if z {
b |= 2
}
w.WriteByte(b)
w.WriteTlv([]byte(d[indexOf2+1:]))
}
}
}
}))
})
}

12
protocol/tlv/t516.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T516() []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x516)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt32(0)
}))
})
}

13
protocol/tlv/t521.go Normal file
View File

@ -0,0 +1,13 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T521() []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x521)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt32(0)
w.WriteUInt16(0)
}))
})
}

13
protocol/tlv/t525.go Normal file
View File

@ -0,0 +1,13 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T525(t536 []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x525)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(1)
w.Write(t536)
}))
})
}

12
protocol/tlv/t52d.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T52D(devInfo []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x52d)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.Write(devInfo)
}))
})
}

12
protocol/tlv/t536.go Normal file
View File

@ -0,0 +1,12 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T536(loginExtraData []byte) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x536)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.Write(loginExtraData)
}))
})
}

14
protocol/tlv/t8.go Normal file
View File

@ -0,0 +1,14 @@
package tlv
import "github.com/Mrs4s/MiraiGo/binary"
func T8(localId uint32) []byte {
return binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0x8)
w.WriteTlv(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt16(0)
w.WriteUInt32(localId)
w.WriteUInt16(0)
}))
})
}

8
protocol/tlv/tlv.go Normal file
View File

@ -0,0 +1,8 @@
package tlv
func GuidFlag() uint32 {
var flag uint32 = 0
flag |= 1 << 24 & 0xFF000000
flag |= 0 << 8 & 0xFF00
return flag
}