diff --git a/bot b/bot deleted file mode 160000 index 1b7712d..0000000 --- a/bot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b7712dc24a614cf5b41dc870fa234a51318188d diff --git a/bot/client.go b/bot/client.go new file mode 100644 index 0000000..5e58254 --- /dev/null +++ b/bot/client.go @@ -0,0 +1,54 @@ +package bot + +import ( + "github.com/Tnze/go-mc/bot/world/entity/player" + "github.com/Tnze/go-mc/net" +) + +// Client is the Object used to access Minecraft server +type Client struct { + conn *net.Conn + Auth + + player.Player + PlayInfo + abilities PlayerAbilities + settings Settings + // wd world //the map data + +} + +//NewClient init and return a new Client +func NewClient() (c *Client) { + c = new(Client) + + //init Client + c.settings = DefaultSettings + c.Name = "Steve" + + return +} + +//PlayInfo content player info in server. +type PlayInfo struct { + Gamemode int //游戏模式 + Hardcore bool //是否是极限模式 + Dimension int //维度 + Difficulty int //难度 + LevelType string //地图类型 + ViewDistance int //视距 + ReducedDebugInfo bool //减少调试信息 + // SpawnPosition Position //主世界出生点 +} + +// PlayerAbilities defines what player can do. +type PlayerAbilities struct { + Flags int8 + FlyingSpeed float32 + FieldofViewModifier float32 +} + +//Position is a 3D vector. +type Position struct { + X, Y, Z int +} diff --git a/bot/ingame.go b/bot/ingame.go new file mode 100644 index 0000000..649c068 --- /dev/null +++ b/bot/ingame.go @@ -0,0 +1,664 @@ +package bot + +import ( + // "bytes" + // "math" + // "time" + "fmt" + + "github.com/Tnze/go-mc/data" + pk "github.com/Tnze/go-mc/net/packet" + // "github.com/Tnze/gomcbot/world/entity" +) + +// //GetPosition return the player's position +// func (p *Player) GetPosition() (x, y, z float64) { +// return p.X, p.Y, p.Z +// } + +// //GetBlockPos return the position of the Block at player's feet +// func (p *Player) GetBlockPos() (x, y, z int) { +// return int(math.Floor(p.X)), int(math.Floor(p.Y)), int(math.Floor(p.Z)) +// } + +// HandleGame recive server packet and response them correctly. +// Note that HandleGame will block if you don't recive from Events. +func (c *Client) HandleGame() error { + for { + //Read packets + p, err := c.conn.ReadPacket() + if err != nil { + return fmt.Errorf("bot: read packet fail: %v", err) + } + //handle packets + err = c.handlePacket(p) + if err != nil { + return fmt.Errorf("handle packet 0x%X error: %v", p.ID, err) + } + } +} + +func (c *Client) handlePacket(p pk.Packet) (err error) { + switch p.ID { + case data.JoinGame: + err = handleJoinGamePacket(c, p) + case data.PluginMessageClientbound: + err = handlePluginPacket(c, p) + case data.ServerDifficulty: + err = handleServerDifficultyPacket(c, p) + case data.SpawnPosition: + err = handleSpawnPositionPacket(c, p) + case data.PlayerAbilitiesClientbound: + err = handlePlayerAbilitiesPacket(c, p) + c.conn.WritePacket( + //ClientSettings packet (serverbound) + pk.Marshal( + data.ClientSettings, + pk.String(c.settings.Locale), + pk.Byte(c.settings.ViewDistance), + pk.VarInt(c.settings.ChatMode), + pk.Boolean(c.settings.ChatColors), + pk.UnsignedByte(c.settings.DisplayedSkinParts), + pk.VarInt(c.settings.MainHand), + ), + ) + case data.HeldItemChangeClientbound: + err = handleHeldItemPacket(c, p) + case data.ChunkData: + ////err = handleChunkDataPacket(c, p) + case data.PlayerPositionAndLookClientbound: + err = handlePlayerPositionAndLookPacket(c, p) + sendPlayerPositionAndLookPacket(c) // to confirm the position + case 0x5A: + // handleDeclareRecipesPacket(g, reader) + case 0x29: + // err = handleEntityLookAndRelativeMove(g, reader) + case 0x3B: + // handleEntityHeadLookPacket(g, reader) + case 0x28: + // err = handleEntityRelativeMovePacket(g, reader) + case data.KeepAliveClientbound: + err = handleKeepAlivePacket(c, p) + case 0x2B: + //handleEntityPacket(g, reader) + case 0x05: + // err = handleSpawnPlayerPacket(g, reader) + case data.WindowItems: + err = handleWindowItemsPacket(c, p) + case data.UpdateHealth: + //// err = handleUpdateHealthPacket(c, p) + case data.ChatMessageClientbound: + ////err = handleChatMessagePacket(c, p) + case data.BlockChange: + ////err = handleBlockChangePacket(c, p) + case data.MultiBlockChange: + ////err = handleMultiBlockChangePacket(c, p) + case 0x1A: + // should assumes that the server has already closed the connection by the time the packet arrives. + + err = fmt.Errorf("disconnect") + case 0x16: + // err = handleSetSlotPacket(g, reader) + case 0x51: + // err = handleSoundEffect(g, reader) + default: + // fmt.Printf("ignore pack id %X\n", p.ID) + } + return +} + +// func handleSoundEffect(g *Client, r *bytes.Reader) error { +// SoundID, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } +// SoundCategory, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } + +// x, err := pk.UnpackInt32(r) +// if err != nil { +// return err +// } +// y, err := pk.UnpackInt32(r) +// if err != nil { +// return err +// } +// z, err := pk.UnpackInt32(r) +// if err != nil { +// return err +// } +// Volume, err := pk.UnpackFloat(r) +// if err != nil { +// return err +// } +// Pitch, err := pk.UnpackFloat(r) +// if err != nil { +// return err +// } +// g.events <- SoundEffectEvent{SoundID, SoundCategory, float64(x) / 8, float64(y) / 8, float64(z) / 8, Volume, Pitch} + +// return nil +// } + +// func handleSetSlotPacket(g *Client, r *bytes.Reader) error { +// windowID, err := r.ReadByte() +// if err != nil { +// return err +// } +// slot, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } +// slotData, err := unpackSolt(r) +// if err != nil { +// return err +// } + +// switch int8(windowID) { +// case 0: +// if slot < 32 || slot > 44 { +// // return fmt.Errorf("slot out of range") +// break +// } +// fallthrough +// case -2: +// g.player.Inventory[slot] = slotData +// g.events <- InventoryChangeEvent(slot) +// } +// return nil +// } + +// func handleMultiBlockChangePacket(c *Client, p pk.Packet) error { +// if !c.settings.ReciveMap { +// return nil +// } + +// var cX, cY pk.Int + +// err := p.Scan(&cX, &cY) +// if err != nil { +// return err +// } + +// c := g.wd.chunks[chunkLoc{int(cX), int(cY)}] +// if c != nil { +// RecordCount, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } + +// for i := int32(0); i < RecordCount; i++ { +// xz, err := r.ReadByte() +// if err != nil { +// return err +// } +// y, err := r.ReadByte() +// if err != nil { +// return err +// } +// BlockID, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } +// x, z := xz>>4, xz&0x0F + +// c.sections[y/16].blocks[x][y%16][z] = Block{id: uint(BlockID)} +// } +// } + +// return nil +// } + +// func handleBlockChangePacket(c *Client, p pk.Packet) error { +// if !c.settings.ReciveMap { +// return nil +// } +// var pos pk.Position +// err := p.Scan(&pos) +// if err != nil { +// return err +// } + +// c := g.wd.chunks[chunkLoc{x >> 4, z >> 4}] +// if c != nil { +// id, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } +// c.sections[y/16].blocks[x&15][y&15][z&15] = Block{id: uint(id)} +// } + +// return nil +// } + +// func handleChatMessagePacket(c *Client, p pk.Packet) error { +// var ( +// s pk.String +// pos pk.Byte +// ) + +// if err := p.Scan(&s, &pos); err != nil { +// return err +// } + +// cm, err := newChatMsg([]byte(s)) +// if err != nil { +// return err +// } + +// return nil +// } + +// func handleUpdateHealthPacket(c *Client, p pk.Packet) (err error) { +// var ( +// Health pk.Float +// Food pk.VarInt +// FoodSaturation pk.Float +// ) + +// err = p.Scan(&Health, &Food, &FoodSaturation) +// if err != nil { +// return +// } + +// c.player.Health = Health +// c.player.Food = Food +// c.player.FoodSaturation = FoodSaturation + +// if c.player.Health < 1 { //player is dead +// sendPlayerPositionAndLookPacket(c) +// time.Sleep(time.Second * 2) //wait for 2 sec make it more like a human +// sendClientStatusPacket(c, 0) //status 0 means perform respawn +// } +// return +// } + +func handleJoinGamePacket(c *Client, p pk.Packet) error { + var ( + eid pk.Int + gamemode pk.UnsignedByte + dimension pk.Int + maxPlayers pk.UnsignedByte + levelType pk.String + viewDistance pk.VarInt + rdi pk.Boolean + ) + err := p.Scan(&eid, &gamemode, &dimension, &maxPlayers, &levelType, &rdi) + if err != nil { + return err + } + + c.EntityID = int(eid) + c.Gamemode = int(gamemode & 0x7) + c.Hardcore = gamemode&0x8 != 0 + c.Dimension = int(dimension) + c.LevelType = string(levelType) + c.ViewDistance = int(viewDistance) + c.ReducedDebugInfo = bool(rdi) + return nil +} + +func handlePluginPacket(c *Client, p pk.Packet) error { + // fmt.Println("Plugin Packet: ", p) + return nil +} + +func handleServerDifficultyPacket(c *Client, p pk.Packet) error { + var difficulty pk.Byte + err := p.Scan(&difficulty) + if err != nil { + return err + } + c.Difficulty = int(difficulty) + return nil +} + +func handleSpawnPositionPacket(c *Client, p pk.Packet) error { + var pos pk.Position + err := p.Scan(&pos) + if err != nil { + return err + } + // c.SpawnPosition.X, c.SpawnPosition.Y, c.SpawnPosition.Z = + // pos.X, pos.Y, pos.Z + return nil +} + +func handlePlayerAbilitiesPacket(g *Client, p pk.Packet) error { + var ( + flags pk.Byte + flySpeed pk.Float + viewMod pk.Float + ) + err := p.Scan(&flags, &flySpeed, &viewMod) + if err != nil { + return err + } + g.abilities.Flags = int8(flags) + g.abilities.FlyingSpeed = float32(flySpeed) + g.abilities.FieldofViewModifier = float32(viewMod) + return nil +} + +func handleHeldItemPacket(c *Client, p pk.Packet) error { + var hi pk.Byte + if err := p.Scan(&hi); err != nil { + return err + } + c.HeldItem = int(hi) + return nil +} + +// func handleChunkDataPacket(g *Client, p pk.Packet) error { +// if !g.settings.ReciveMap { +// return nil +// } + +// c, x, y, err := unpackChunkDataPacket(p, g.Info.Dimension == 0) +// g.wd.chunks[chunkLoc{x, y}] = c +// return err +// } + +// var isSpawn bool + +func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error { + var ( + x, y, z pk.Double + yaw, pitch pk.Float + flags pk.Byte + TeleportID pk.VarInt + ) + + err := p.Scan(&x, &y, &z, &yaw, &pitch, &flags, &TeleportID) + if err != nil { + return err + } + + if flags&0x01 == 0 { + c.X = float64(x) + } else { + c.X += float64(x) + } + if flags&0x02 == 0 { + c.Y = float64(y) + } else { + c.Y += float64(y) + } + if flags&0x04 == 0 { + c.Z = float64(z) + } else { + c.Z += float64(z) + } + if flags&0x08 == 0 { + c.Yaw = float32(yaw) + } else { + c.Yaw += float32(yaw) + } + if flags&0x10 == 0 { + c.Pitch = float32(pitch) + } else { + c.Pitch += float32(pitch) + } + + //Confirm + return c.conn.WritePacket(pk.Marshal( + data.TeleportConfirm, + pk.VarInt(TeleportID), + )) +} + +// func handleDeclareRecipesPacket(g *Client, r *bytes.Reader) { +// //Ignore Declare Recipes Packet + +// // NumRecipes, index := pk.UnpackVarInt(p.Data) +// // for i := 0; i < int(NumRecipes); i++ { +// // RecipeID, len := pk.UnpackString(p.Data[index:]) +// // index += len +// // Type, len := pk.UnpackString(p.Data[index:]) +// // index += len +// // switch Type { +// // case "crafting_shapeless": +// // } +// // } +// } + +// func handleEntityLookAndRelativeMove(g *Client, r *bytes.Reader) error { +// ID, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } +// E := g.wd.Entities[ID] +// if E != nil { +// P, ok := E.(*Player) +// if !ok { +// return nil +// } +// DeltaX, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } +// DeltaY, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } +// DeltaZ, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } + +// yaw, err := r.ReadByte() +// if err != nil { +// return err +// } + +// pitch, err := r.ReadByte() +// if err != nil { +// return err +// } +// P.Yaw += float32(yaw) * (1.0 / 256) +// P.Pitch += float32(pitch) * (1.0 / 256) + +// og, err := r.ReadByte() +// if err != nil { +// return err +// } +// P.OnGround = og != 0x00 + +// P.X += float64(DeltaX) / 128 +// P.Y += float64(DeltaY) / 128 +// P.Z += float64(DeltaZ) / 128 +// } +// return nil +// } + +// func handleEntityHeadLookPacket(g *Client, r *bytes.Reader) { + +// } + +// func handleEntityRelativeMovePacket(g *Client, r *bytes.Reader) error { +// ID, err := pk.UnpackVarInt(r) +// if err != nil { +// return err +// } +// E := g.wd.Entities[ID] +// if E != nil { +// P, ok := E.(*Player) +// if !ok { +// return nil +// } +// DeltaX, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } +// DeltaY, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } +// DeltaZ, err := pk.UnpackInt16(r) +// if err != nil { +// return err +// } + +// og, err := r.ReadByte() +// if err != nil { +// return err +// } +// P.OnGround = og != 0x00 + +// P.X += float64(DeltaX) / 128 +// P.Y += float64(DeltaY) / 128 +// P.Z += float64(DeltaZ) / 128 +// } +// return nil +// } + +func handleKeepAlivePacket(c *Client, p pk.Packet) error { + var KeepAliveID pk.Long + if err := p.Scan(&KeepAliveID); err != nil { + return err + } + //Response + return c.conn.WritePacket(pk.Marshal( + data.KeepAliveServerbound, + KeepAliveID, + )) +} + +// func handleEntityPacket(g *Client, r *bytes.Reader) { +// // initialize an entity. +// } + +// func handleSpawnPlayerPacket(g *Client, r *bytes.Reader) (err error) { +// np := new(Player) +// np.entityID, err = pk.UnpackVarInt(r) +// if err != nil { +// return +// } +// np.UUID[0], err = pk.UnpackInt64(r) +// if err != nil { +// return +// } +// np.UUID[1], err = pk.UnpackInt64(r) +// if err != nil { +// return +// } +// np.X, err = pk.UnpackDouble(r) +// if err != nil { +// return +// } +// np.Y, err = pk.UnpackDouble(r) +// if err != nil { +// return +// } +// np.Z, err = pk.UnpackDouble(r) +// if err != nil { +// return +// } + +// yaw, err := r.ReadByte() +// if err != nil { +// return err +// } + +// pitch, err := r.ReadByte() +// if err != nil { +// return err +// } + +// np.Yaw = float32(yaw) * (1.0 / 256) +// np.Pitch = float32(pitch) * (1.0 / 256) + +// g.wd.Entities[np.entityID] = np //把该玩家添加到全局实体表里面 +// return nil +// } + +func handleWindowItemsPacket(g *Client, p pk.Packet) (err error) { + // var ( + // WindowID pk.Byte + // solts entity.Solt + // ) + // err = p.Scan(&WindowID, &solts) + // if err != nil { + // return + // } + + // switch WindowID { + // case 0: //is player inventory + // g.Inventory = solts + // } + return nil +} + +func sendPlayerPositionAndLookPacket(c *Client) { + c.conn.WritePacket(pk.Marshal( + data.PlayerPositionAndLookServerbound, + pk.Double(c.X), + pk.Double(c.Y), + pk.Double(c.Z), + pk.Float(c.Yaw), + pk.Float(c.Pitch), + pk.Boolean(c.OnGround), + )) +} + +// func sendPlayerLookPacket(g *Client) { +// var data []byte +// data = append(data, pk.PackFloat(g.player.Yaw)...) +// data = append(data, pk.PackFloat(g.player.Pitch)...) +// data = append(data, pk.PackBoolean(g.player.OnGround)) +// g.sendChan <- pk.Packet{ +// ID: 0x12, +// Data: data, +// } +// } + +// func sendPlayerPositionPacket(g *Client) { +// var data []byte +// data = append(data, pk.PackDouble(g.player.X)...) +// data = append(data, pk.PackDouble(g.player.Y)...) +// data = append(data, pk.PackDouble(g.player.Z)...) +// data = append(data, pk.PackBoolean(g.player.OnGround)) + +// g.sendChan <- pk.Packet{ +// ID: 0x10, +// Data: data, +// } +// } + +// func sendClientStatusPacket(g *Client, status int32) { +// data := pk.PackVarInt(status) +// g.sendChan <- pk.Packet{ +// ID: 0x03, +// Data: data, +// } +// } + +// //hand could be 0: main hand, 1: off hand +// func sendAnimationPacket(g *Client, hand int32) { +// data := pk.PackVarInt(hand) +// g.sendChan <- pk.Packet{ +// ID: 0x27, +// Data: data, +// } +// } + +// func sendPlayerDiggingPacket(g *Client, status int32, x, y, z int, face Face) { +// data := pk.PackVarInt(status) +// data = append(data, pk.PackPosition(x, y, z)...) +// data = append(data, byte(face)) + +// g.sendChan <- pk.Packet{ +// ID: 0x18, +// Data: data, +// } +// } + +// func sendUseItemPacket(g *Client, hand int32) { +// data := pk.PackVarInt(hand) +// g.sendChan <- pk.Packet{ +// ID: 0x2A, +// Data: data, +// } +// } diff --git a/bot/login.go b/bot/login.go new file mode 100644 index 0000000..d1dd70e --- /dev/null +++ b/bot/login.go @@ -0,0 +1,227 @@ +package bot + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + + "github.com/Tnze/go-mc/net/CFB8" + pk "github.com/Tnze/go-mc/net/packet" +) + +// Auth includes a account +type Auth struct { + Name string + UUID string + AsTk string +} + +// 加密请求 +func handleEncryptionRequest(c *Client, pack pk.Packet) error { + //创建AES对称加密密钥 + key, encoStream, decoStream := newSymmetricEncryption() + + //解析EncryptionRequest包 + var er encryptionRequest + if err := pack.Scan(&er); err != nil { + return err + } + err := loginAuth(c.AsTk, c.Name, c.Auth.UUID, key, er) //向Mojang验证 + if err != nil { + return fmt.Errorf("login fail: %v", err) + } + + // 响应加密请求 + var p pk.Packet // Encryption Key Response + p, err = genEncryptionKeyResponse(key, er.PublicKey, er.VerifyToken) + if err != nil { + return fmt.Errorf("gen encryption key response fail: %v", err) + } + err = c.conn.WritePacket(p) + if err != nil { + return err + } + + // 设置连接加密 + c.conn.SetCipher(encoStream, decoStream) + return nil +} + +type encryptionRequest struct { + ServerID string + PublicKey []byte + VerifyToken []byte +} + +func (e *encryptionRequest) Decode(r io.ByteReader) error { + var serverID pk.String + if err := serverID.Decode(r); err != nil { + return err + } + + var publicKeyLength, verifyTokenLength pk.VarInt + + if err := publicKeyLength.Decode(r); err != nil { + return err + } + publicKey, err := pk.ReadNBytes(r, int(publicKeyLength)) + if err != nil { + return err + } + + if err := verifyTokenLength.Decode(r); err != nil { + return err + } + verifyToken, err := pk.ReadNBytes(r, int(verifyTokenLength)) + if err != nil { + return err + } + + e.ServerID = string(serverID) + e.PublicKey = publicKey + e.VerifyToken = verifyToken + return nil +} + +// authDigest computes a special SHA-1 digest required for Minecraft web +// authentication on Premium servers (online-mode=true). +// Source: http://wiki.vg/Protocol_Encryption#Server +// +// Also many, many thanks to SirCmpwn and his wonderful gist (C#): +// https://gist.github.com/SirCmpwn/404223052379e82f91e6 +func authDigest(serverID string, sharedSecret, publicKey []byte) string { + h := sha1.New() + h.Write([]byte(serverID)) + h.Write(sharedSecret) + h.Write(publicKey) + hash := h.Sum(nil) + + // Check for negative hashes + negative := (hash[0] & 0x80) == 0x80 + if negative { + hash = twosComplement(hash) + } + + // Trim away zeroes + res := strings.TrimLeft(fmt.Sprintf("%x", hash), "0") + if negative { + res = "-" + res + } + + return res +} + +// little endian +func twosComplement(p []byte) []byte { + carry := true + for i := len(p) - 1; i >= 0; i-- { + p[i] = byte(^p[i]) + if carry { + carry = p[i] == 0xff + p[i]++ + } + } + return p +} + +type profile struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type request struct { + AccessToken string `json:"accessToken"` + SelectedProfile profile `json:"selectedProfile"` + ServerID string `json:"serverId"` +} + +func loginAuth(AsTk, name, UUID string, shareSecret []byte, er encryptionRequest) error { + digest := authDigest(er.ServerID, shareSecret, er.PublicKey) + + client := http.Client{} + requestPacket, err := json.Marshal( + request{ + AccessToken: AsTk, + SelectedProfile: profile{ + ID: UUID, + Name: name, + }, + ServerID: digest, + }, + ) + if err != nil { + return fmt.Errorf("create request packet to authenticate faile: %v", err) + } + + PostRequest, err := http.NewRequest(http.MethodPost, "https://sessionserver.mojang.com/session/minecraft/join", + bytes.NewReader(requestPacket)) + if err != nil { + return fmt.Errorf("make request error: %v", err) + } + PostRequest.Header.Set("User-Agent", "gomcbot") + PostRequest.Header.Set("Connection", "keep-alive") + resp, err := client.Do(PostRequest) + if err != nil { + return fmt.Errorf("post fail: %v", err) + } + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + if resp.Status != "204 No Content" { + return fmt.Errorf("auth fail: %s", string(body)) + } + return nil +} + +// AES/CFB8 with random key +func newSymmetricEncryption() (key []byte, encoStream, decoStream cipher.Stream) { + key = make([]byte, 16) + rand.Read(key) //生成密钥 + + b, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + decoStream = CFB8.NewCFB8Decrypt(b, key) + encoStream = CFB8.NewCFB8Encrypt(b, key) + return +} + +func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte) (erp pk.Packet, err error) { + iPK, err := x509.ParsePKIXPublicKey(publicKey) // Decode Public Key + if err != nil { + err = fmt.Errorf("decode public key fail: %v", err) + return + } + rsaKey := iPK.(*rsa.PublicKey) + cryptPK, err := rsa.EncryptPKCS1v15(rand.Reader, rsaKey, shareSecret) + if err != nil { + err = fmt.Errorf("encryption share secret fail: %v", err) + return + } + verifyT, err := rsa.EncryptPKCS1v15(rand.Reader, rsaKey, verifyToken) + if err != nil { + err = fmt.Errorf("encryption verfy tokenfail: %v", err) + return + } + var data []byte + data = append(data, pk.VarInt(int32(len(cryptPK))).Encode()...) + data = append(data, cryptPK...) + data = append(data, pk.VarInt(int32(len(verifyT))).Encode()...) + data = append(data, verifyT...) + erp = pk.Packet{ + ID: 0x01, + Data: data, + } + return +} diff --git a/bot/mcbot.go b/bot/mcbot.go new file mode 100644 index 0000000..edea34a --- /dev/null +++ b/bot/mcbot.go @@ -0,0 +1,125 @@ +package bot + +import ( + // "bufio" + // "bytes" + "fmt" + // "net" + + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" +) + +//ProtocalVersion is the protocal version +// 477 for 1.14 +const ProtocalVersion = 477 + +// PingAndList chack server status and list online player +// Return a JSON string about server status. +// see JSON format at https://wiki.vg/Server_List_Ping#Response +func PingAndList(addr string, port int) (string, error) { + conn, err := net.DialMC(fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + return "", err + } + + //握手 + err = conn.WritePacket( + //Handshake Packet + pk.Marshal( + 0x00, //Handshake packet ID + pk.VarInt(ProtocalVersion), //Protocal version + pk.String(addr), //Server's address + pk.UnsignedShort(port), + pk.Byte(1), + )) + if err != nil { + return "", fmt.Errorf("bot: send handshake packect fail: %v", err) + } + + //请求服务器状态 + err = conn.WritePacket(pk.Marshal(0)) + if err != nil { + return "", fmt.Errorf("bot: send list packect fail: %v", err) + } + + //服务器返回状态 + recv, err := conn.ReadPacket() + if err != nil { + return "", fmt.Errorf("bot: recv list packect fail: %v", err) + } + var s pk.String + err = recv.Scan(&s) + return string(s), err +} + +// JoinServer connect a Minecraft server for playing the game. +func (c *Client) JoinServer(addr string, port int) (err error) { + //Connect + c.conn, err = net.DialMC(fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + err = fmt.Errorf("bot: connect server fail: %v", err) + return + } + + //Handshake + err = c.conn.WritePacket( + //Handshake Packet + pk.Marshal( + 0x00, //Handshake packet ID + pk.VarInt(ProtocalVersion), //Protocal version + pk.String(addr), //Server's address + pk.UnsignedShort(port), + pk.Byte(2), + )) + if err != nil { + err = fmt.Errorf("bot: send handshake packect fail: %v", err) + return + } + + //Login + err = c.conn.WritePacket( + //LoginStart Packet + pk.Marshal(0, pk.String(c.Name))) + if err != nil { + err = fmt.Errorf("bot: send login start packect fail: %v", err) + return + } + + for { + //Recive Packet + var pack pk.Packet + pack, err = c.conn.ReadPacket() + if err != nil { + err = fmt.Errorf("bot: recv packet for Login fail: %v", err) + return + } + + //Handle Packet + switch pack.ID { + case 0x00: //Disconnect + var reason pk.String + err = pack.Scan(&reason) + if err != nil { + err = fmt.Errorf("bot: read Disconnect message fail: %v", err) + } else { + err = fmt.Errorf("bot: connect disconnected by server: %s", reason) + } + return + case 0x01: //Encryption Request + handleEncryptionRequest(c, pack) + case 0x02: //Login Success + // uuid, l := pk.UnpackString(pack.Data) + // name, _ := unpackString(pack.Data[l:]) + return //switches the connection state to PLAY. + case 0x03: //Set Compression + var threshold pk.VarInt + if err := pack.Scan(&threshold); err != nil { + return fmt.Errorf("bot: set compression fail: %v", err) + } + c.conn.SetThreshold(int(threshold)) + case 0x04: //Login Plugin Request + fmt.Println("Waring Login Plugin Request") + } + } +} diff --git a/bot/settings.go b/bot/settings.go new file mode 100644 index 0000000..73af757 --- /dev/null +++ b/bot/settings.go @@ -0,0 +1,36 @@ +package bot + +// Settings of client +type Settings struct { + Locale string //地区 + ViewDistance int //视距 + ChatMode int //聊天模式 + ChatColors bool //聊天颜色 + DisplayedSkinParts uint8 //皮肤显示 + MainHand int //主手 + ReciveMap bool //接收地图数据 +} + +/* + Used by Settings.DisplayedSkinParts. + For each bits set if shows match part. +*/ +const ( + _ = 1 << iota + Jacket + LeftSleeve + RightSleeve + LeftPantsLeg + RightPantsLeg + Hat +) + +//DefaultSettings are the default settings of client +var DefaultSettings = Settings{ + Locale: "zh_CN", + ViewDistance: 15, + ChatMode: 0, + DisplayedSkinParts: Jacket | LeftSleeve | RightSleeve | LeftPantsLeg | RightPantsLeg | Hat, + MainHand: 1, + ReciveMap: true, +} diff --git a/bot/world/chunk.go b/bot/world/chunk.go new file mode 100644 index 0000000..cf17ebf --- /dev/null +++ b/bot/world/chunk.go @@ -0,0 +1,157 @@ +package gomcbot + +// import ( +// "bytes" +// "fmt" +// pk "github.com/Tnze/gomcbot/network/packet" +// "io" +// ) + +// func unpackChunkDataPacket(p *pk.Packet, hasSkyLight bool) (c *Chunk, x, y int, err error) { +// reader := bytes.NewReader(p.Data) +// //区块坐标 +// X, err := pk.UnpackInt32(reader) +// if err != nil { +// return nil, 0, 0, err +// } +// Y, err := pk.UnpackInt32(reader) +// if err != nil { +// return nil, 0, 0, err +// } +// // fmt.Println("Chunk: (", X, ", ", Y, ")") //Debug: Show Chunk loc +// fc, err := reader.ReadByte() +// if err != nil { +// return nil, 0, 0, err +// } +// FullChunk := fc != 0x00 + +// //主掩码 +// PrimaryBitMask, err := pk.UnpackVarInt(reader) +// if err != nil { +// return nil, 0, 0, err +// } + +// //区块数据 +// Size, err := pk.UnpackVarInt(reader) +// if err != nil { +// return nil, 0, 0, err +// } +// Data := make([]byte, Size) +// _, err = io.ReadAtLeast(reader, Data, int(Size)) +// if err != nil { +// return nil, 0, 0, err +// } + +// //实体信息 +// // NumberofBlockEntities, len := pk.UnpackVarInt(p.Data[index:]) +// // index += len + +// //解析区块数据 +// cc, err := readChunkColumn(FullChunk, PrimaryBitMask, bytes.NewReader(Data), hasSkyLight) +// if err != nil { +// panic(err) +// } +// return cc, int(X), int(Y), err +// } + +// func readChunkColumn(isFull bool, mask int32, data *bytes.Reader, hasSkyLight bool) (*Chunk, error) { +// var c Chunk +// for sectionY := 0; sectionY < 16; sectionY++ { +// if (mask & (1 << uint(sectionY))) != 0 { // Is the given bit set in the mask? +// BitsPerBlock, err := data.ReadByte() +// if err != nil { +// return nil, fmt.Errorf("read BitsPerBlock fail: %v", err) +// } +// //读调色板 +// var palette []uint +// if BitsPerBlock < 9 { +// length, err := pk.UnpackVarInt(data) +// if err != nil { +// return nil, fmt.Errorf("read palette (id len) fail: %v", err) +// } +// palette = make([]uint, length) + +// for id := uint(0); id < uint(length); id++ { +// stateID, err := pk.UnpackVarInt(data) +// if err != nil { +// return nil, fmt.Errorf("read palette (id) fail: %v", err) +// } + +// palette[id] = uint(stateID) +// } +// } + +// //Section数据 +// DataArrayLength, err := pk.UnpackVarInt(data) +// if err != nil { +// return nil, fmt.Errorf("read DataArrayLength fail: %v", err) +// } + +// DataArray := make([]int64, DataArrayLength) +// for i := 0; i < int(DataArrayLength); i++ { +// DataArray[i], err = pk.UnpackInt64(data) +// if err != nil { +// return nil, fmt.Errorf("read DataArray fail: %v", err) +// } +// } +// //用数据填充区块 +// fillSection(&c.sections[sectionY], perBits(BitsPerBlock), DataArray, palette) + +// //throw BlockLight data +// _, err = pk.ReadNBytes(data, 2048) +// if err != nil { +// return nil, fmt.Errorf("read BlockLight fail: %v", err) +// } + +// if hasSkyLight { +// //throw SkyLight data +// _, err = pk.ReadNBytes(data, 2048) +// if err != nil { +// return nil, fmt.Errorf("read SkyLight fail: %v", err) +// } +// } +// } +// } +// if isFull { //need recive Biomes datas +// _, err := pk.ReadNBytes(data, 256*4) +// if err != nil { +// return nil, fmt.Errorf("read Biomes fail: %v", err) +// } +// } + +// // fmt.Println(c) +// return &c, nil +// } + +// const defaultBitsPerBlock = 14 + +// func perBits(BitsPerBlock byte) uint { +// switch { +// case BitsPerBlock <= 4: +// return 4 +// case BitsPerBlock < 9: +// return uint(BitsPerBlock) +// default: +// return defaultBitsPerBlock +// } +// } + +// func fillSection(s *Section, bpb uint, DataArray []int64, palette []uint) { +// mask := uint(1<>= offset % 64 +// if offset%64 > 64-bpb { +// l := bpb + offset%64 - 64 +// data &= uint(DataArray[offset/64+1] << l) +// } +// data &= mask + +// if bpb < 9 { +// s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = palette[data] +// } else { +// s.blocks[n%16][n/(16*16)][n%(16*16)/16].id = data +// } +// } +// } diff --git a/bot/world/entity/entity.go b/bot/world/entity/entity.go new file mode 100644 index 0000000..9c693d4 --- /dev/null +++ b/bot/world/entity/entity.go @@ -0,0 +1,9 @@ +package entity + +//Entity is the entity of minecraft +type Entity struct { + EntityID int //实体ID +} + +type Solt struct { +} diff --git a/bot/world/entity/player/player.go b/bot/world/entity/player/player.go new file mode 100644 index 0000000..a533299 --- /dev/null +++ b/bot/world/entity/player/player.go @@ -0,0 +1,20 @@ +package player + +import "github.com/Tnze/go-mc/bot/world/entity" + +// Player includes the player's status. +type Player struct { + entity.Entity + UUID [2]int64 //128bit UUID + + X, Y, Z float64 + Yaw, Pitch float32 + OnGround bool + + HeldItem int //拿着的物品栏位 + Inventory []entity.Solt + + Health float32 //血量 + Food int32 //饱食度 + FoodSaturation float32 //食物饱和度 +} diff --git a/bot/world/motion.go b/bot/world/motion.go new file mode 100644 index 0000000..39f5c83 --- /dev/null +++ b/bot/world/motion.go @@ -0,0 +1,78 @@ +package gomcbot + +// import ( +// "math" +// "time" +// ) + +// // SetPosition method move your character around. +// // Server will ignore this if changes too much. +// func (g *Client) SetPosition(x, y, z float64, onGround bool) { +// g.motion <- func() { +// g.player.X, g.player.Y, g.player.Z = x, y, z +// g.player.OnGround = onGround +// sendPlayerPositionPacket(g) //向服务器更新位置 +// } +// } + +// // LookAt method turn player's hand and make it look at a point. +// func (g *Client) LookAt(x, y, z float64) { +// x0, y0, z0 := g.player.X, g.player.Y, g.player.Z +// x, y, z = x-x0, y-y0, z-z0 + +// r := math.Sqrt(x*x + y*y + z*z) +// yaw := -math.Atan2(x, z) / math.Pi * 180 +// for yaw < 0 { +// yaw = 360 + yaw +// } +// pitch := -math.Asin(y/r) / math.Pi * 180 + +// g.LookYawPitch(float32(yaw), float32(pitch)) +// } + +// // LookYawPitch set player's hand to the direct by yaw and pitch. +// // yaw can be [0, 360) and pitch can be (-180, 180). +// // if |pitch|>90 the player's hand will be very strange. +// func (g *Client) LookYawPitch(yaw, pitch float32) { +// g.motion <- func() { +// g.player.Yaw, g.player.Pitch = yaw, pitch +// sendPlayerLookPacket(g) //向服务器更新朝向 +// } +// } + +// // SwingHand sent when the player's arm swings. +// // if hand is true, swing the main hand +// func (g *Client) SwingHand(hand bool) { +// if hand { +// sendAnimationPacket(g, 0) +// } else { +// sendAnimationPacket(g, 1) +// } +// } + +// // Dig a block in the position and wait for it's breaked +// func (g *Client) Dig(x, y, z int) error { +// b := g.GetBlock(x, y, z).id +// sendPlayerDiggingPacket(g, 0, x, y, z, Top) //start +// sendPlayerDiggingPacket(g, 2, x, y, z, Top) //end + +// for { +// time.Sleep(time.Millisecond * 50) +// if g.GetBlock(x, y, z).id != b { +// break +// } +// g.SwingHand(true) +// } + +// return nil +// } + +// // UseItem use the item in hand. +// // if hand is true, swing the main hand +// func (g *Client) UseItem(hand bool) { +// if hand { +// sendUseItemPacket(g, 0) +// } else { +// sendUseItemPacket(g, 1) +// } +// } diff --git a/bot/world/world.go b/bot/world/world.go new file mode 100644 index 0000000..d623cad --- /dev/null +++ b/bot/world/world.go @@ -0,0 +1,68 @@ +package gomcbot + +// //World record all of the things in the world where player at +// type world struct { +// Entities map[int32]Entity +// chunks map[chunkLoc]*Chunk +// } + +// //Chunk store a 256*16*16 clolumn blocks +// type Chunk struct { +// sections [16]Section +// } + +// //Section store a 16*16*16 cube blocks +// type Section struct { +// blocks [16][16][16]Block +// } + +// //Block is the base of world +// type Block struct { +// id uint +// } + +// type chunkLoc struct { +// X, Y int +// } + +// //Entity 表示一个实体 +// type Entity interface { +// EntityID() int32 +// } + +// //Face is a face of a block +// type Face byte + +// // All six faces in a block +// const ( +// Bottom Face = iota +// Top +// North +// South +// West +// East +// ) + +// // getBlock return the block in the position (x, y, z) +// func (w *world) getBlock(x, y, z int) Block { +// c := w.chunks[chunkLoc{x >> 4, z >> 4}] +// if c != nil { +// cx, cy, cz := x&15, y&15, z&15 +// /* +// n = n&(16-1) + +// is equal to + +// n %= 16 +// if n < 0 { n += 16 } +// */ + +// return c.sections[y/16].blocks[cx][cy][cz] +// } + +// return Block{id: 0} +// } + +// func (b Block) String() string { +// return blockNameByID[b.id] +// } diff --git a/bot/world/world_test.go b/bot/world/world_test.go new file mode 100644 index 0000000..abcd8f8 --- /dev/null +++ b/bot/world/world_test.go @@ -0,0 +1,9 @@ +package gomcbot + +// import "testing" + +// func TestBlockString(t *testing.T) { +// for i := uint(0); i < 8598+1; i++ { +// t.Log(Block{id: i}) +// } +// }