This commit is contained in:
JunDao
2019-05-01 15:22:42 +08:00
parent 2b58eb07b5
commit 0d436609ce
21 changed files with 91491 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"go.inferGopath": false
}

View File

@ -0,0 +1,104 @@
package authenticate
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
// Agent is a struct of auth
type Agent struct {
Name string `json:"name"`
Version int `json:"version"`
}
// Payload is a authenticate request struct
type Payload struct {
Agent `json:"agent"`
UserName string `json:"username"`
Password string `json:"password"`
ClientToken string `json:"clientToken"`
RequestUser bool `json:"requestUser"`
}
// Authenticate authenticates a user using their password.
func Authenticate(user, passwd string) (respData Response, err error) {
j, err := json.Marshal(Payload{
Agent: Agent{
Name: "Minecraft",
Version: 1,
},
UserName: user,
Password: passwd,
ClientToken: "gomcbotauther",
RequestUser: true,
})
// fmt.Println(string(j))
if err != nil {
err = fmt.Errorf("encoding json fail: %v", err)
return
}
//Post
client := http.Client{}
PostRequest, err := http.NewRequest(http.MethodPost, "https://authserver.mojang.com/authenticate",
bytes.NewReader(j))
if err != nil {
err = fmt.Errorf("make request error: %v", err)
return
}
PostRequest.Header.Set("User-Agent", "gomcbot")
PostRequest.Header.Set("Connection", "keep-alive")
resp, err := client.Do(PostRequest)
if err != nil {
err = fmt.Errorf("post authenticate fail: %v", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("read authenticate resp fail: %v", err)
return
}
err = json.Unmarshal(body, &respData)
if err != nil {
err = fmt.Errorf("unmarshal json data fail: %v", err)
return
}
if respData.Error != "" {
err = fmt.Errorf("authenticate fail: {error: %q, errorMessage: %q, cause: %q}",
respData.Error, respData.ErrorMessage, respData.Cause)
return
}
return
}
// Response is the response from Mojang's auth server
type Response struct {
Error string `json:"error"`
ErrorMessage string `json:"errorMessage"`
Cause string `json:"cause"`
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"` // identical to the one received
AvailableProfiles []struct {
ID string `json:"ID"` // hexadecimal
Name string `json:"name"`
Legacy bool `json:"legacy"` // In practice, this field only appears in the response if true. Default to false.
} `json:"availableProfiles"` // only present if the agent field was received
SelectedProfile struct { // only present if the agent field was received
ID string `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
} `json:"selectedProfile"`
User struct { // only present if requestUser was true in the request payload
ID string `json:"id"` // hexadecimal
Properties []struct {
Name string `json:"name"`
Value string `json:"value"`
}
} `json:"user"`
}

View File

@ -0,0 +1,32 @@
package authenticate
import (
"encoding/json"
"fmt"
"testing"
)
func TestEncodingPayload(t *testing.T) {
j, err := json.Marshal(Payload{
Agent: Agent{
Name: "Minecraft",
Version: 1,
},
UserName: "mojang account name",
Password: "mojang account password",
ClientToken: "client identifier",
RequestUser: true,
})
if err != nil {
t.Fatal(err)
}
t.Log(string(j))
}
func ExampleAuthenticate() {
resp, err := Authenticate("", "")
if err != nil {
panic(err)
}
fmt.Println(resp)
}

1
bot Submodule

Submodule bot added at 1b7712dc24

105
chat/chatMsg.go Normal file
View File

@ -0,0 +1,105 @@
package chat
import (
"encoding/json"
"fmt"
"strings"
"github.com/Tnze/go-mc/data"
)
//Message is a message sent by other
type Message jsonChat
type jsonChat struct {
Text string `json:"text"`
Bold bool `json:"bold"` //粗体
Italic bool `json:"Italic"` //斜体
UnderLined bool `json:"underlined"` //下划线
StrikeThrough bool `json:"strikethrough"` //删除线
Obfuscated bool `json:"obfuscated"` //随机
Color string `json:"color"`
Translate string `json:"translate"`
With []json.RawMessage `json:"with"` // How can go handle an JSON array with Object and String?
Extra []jsonChat `json:"extra"`
}
//UnmarshalJSON decode json to Message
func (m *Message) UnmarshalJSON(jsonMsg []byte) (err error) {
if jsonMsg[0] == '"' {
err = json.Unmarshal(jsonMsg, &m.Text)
} else {
err = json.Unmarshal(jsonMsg, (*jsonChat)(m))
}
return
}
var colors = map[string]int{
"black": 30,
"dark_blue": 34,
"dark_green": 32,
"dark_aqua": 36,
"dark_red": 31,
"dark_purple": 35,
"gold": 33,
"gray": 37,
"dark_gray": 90,
"blue": 94,
"green": 92,
"aqua": 96,
"red": 91,
"light_purple": 95,
"yellow": 93,
"white": 97,
}
// String return the message with escape sequence for ansi color.
// On windows, you may want print this string using
// github.com/mattn/go-colorable.
func (m Message) String() string {
var msg, format strings.Builder
if m.Bold {
format.WriteString("1;")
}
if m.Italic {
format.WriteString("3;")
}
if m.UnderLined {
format.WriteString("4;")
}
if m.StrikeThrough {
format.WriteString("9;")
}
if m.Color != "" {
fmt.Fprintf(&format, "%d;", colors[m.Color])
}
if format.Len() > 0 {
msg.WriteString("\033[" + format.String()[:format.Len()-1] + "m")
}
msg.WriteString(m.Text)
if format.Len() > 0 {
msg.WriteString("\033[0m")
}
//handle translate
if m.Translate != "" {
args := make([]interface{}, len(m.With))
for i, v := range m.With {
var arg Message
arg.UnmarshalJSON(v) //ignore error
args[i] = arg
}
fmt.Fprintf(&msg, data.EnUs[m.Translate], args...)
}
if m.Extra != nil {
for i := range m.Extra {
msg.WriteString(Message(m.Extra[i]).String())
}
}
return msg.String()
}

53
chat/chatMsg_test.go Normal file
View File

@ -0,0 +1,53 @@
package chat
import (
// "fmt"
//"github.com/mattn/go-colorable"//On Windows need
"testing"
)
/*
"translation.test.none": "Hello, world!",
"translation.test.complex": "Prefix, %s %[2]s again %s and %[1]s lastly %s and also %[1]s again!"
"Prefix, str1str2 again str3 and str1 lastly str2 and also str1 again!"
"Prefix, str1str2 again str2 and str1 lastly str3 and also str1 again!"
"translation.test.escape": "%%s %%%s %%%%s %%%%%s",
"translation.test.invalid": "hi %",
"translation.test.invalid2": "hi % s",
"translation.test.args": "%s %s",
"translation.test.world":
*/
var jsons = []string{
`{"extra":[{"color":"green","text":"故我依然"},{"color":"white","text":"™ "},{"color":"gray","text":"Kun_QwQ"},{"color":"white","text":": 为什么想要用炼药锅灭火时总是跳不进去"}],"text":""}`,
`{"translate":"chat.type.text","with":[{"insertion":"Xi_Xi_Mi","clickEvent":{"action":"suggest_command","value":"/tell Xi_Xi_Mi "},"hoverEvent":{"action":"show_entity","value":{"text":"{name:\"{\\\"text\\\":\\\"Xi_Xi_Mi\\\"}\",id:\"c1445a67-7551-4d7e-813d-65ef170ae51f\",type:\"minecraft:player\"}"}},"text":"Xi_Xi_Mi"},"好像是这个id。。"]}`,
`{"translate":"translation.test.none"}`,
//`{"translate":"translation.test.complex","with":["str1","str2","str3"]}`,
`{"translate":"translation.test.escape","with":["str1","str2"]}`,
`{"translate":"translation.test.args","with":["str1","str2"]}`,
`{"translate":"translation.test.world"}`,
}
var texts = []string{
"\033[92m故我依然\033[0m\033[97m™ \033[0m\033[37mKun_QwQ\033[0m\033[97m: 为什么想要用炼药锅灭火时总是跳不进去\033[0m",
"<Xi_Xi_Mi> 好像是这个id。。",
"Hello, world!",
//"Prefix, str1str2 again str2 and str1 lastly str3 and also str1 again!",
"%s %str1 %%s %%str2",
"str1 str2",
"world",
}
func TestChatMsgFormatString(t *testing.T) {
for i, v := range jsons {
var cm Message
err := cm.UnmarshalJSON([]byte(v))
if err != nil {
t.Error(err)
}
if cm.String() != texts[i] {
t.Errorf("gets %q, wants %q", cm, texts[i])
}
}
}

21
cmd/daze/daze.go Normal file
View File

@ -0,0 +1,21 @@
package main
import (
bot "github.com/Tnze/gomcbot"
"log"
)
func main() {
c := bot.NewClient()
err := c.JoinServer("localhost", 25565)
if err != nil {
log.Fatal(err)
}
log.Println("Login success")
err = c.HandleGame()
if err != nil {
log.Fatal(err)
}
}

67
cmd/examples_test.go Normal file
View File

@ -0,0 +1,67 @@
package cmd
import (
"fmt"
bot "github.com/Tnze/gomcbot"
auth "github.com/Tnze/gomcbot/util/authenticate"
)
func ExamplePingAndList() {
resp, err := bot.PingAndList("localhost", 25565)
if err != nil {
panic(err)
}
// see format of resp at https://wiki.vg/Server_List_Ping#Response
fmt.Println(resp)
}
func Example_joinOfflineServer() {
c := bot.NewClient()
err := c.JoinServer("jdao.online", 25566)
if err != nil {
panic(err)
}
//Handle game
// events := game.GetEvents()
// go game.HandleGame()
// for e := range events { //Reciving events
// switch e.(type) {
// case bot.PlayerSpawnEvent:
// fmt.Println("Player is spawned!")
// }
// }
}
func Example_joinOnlineServer() {
c := bot.NewClient()
//Login
// This is the basic authenticate function.
// Maybe you could get more control of login process by using
// https://github.com/JoshuaDoes/go-yggdrasil.
resp, err := auth.Authenticate("email", "password")
if err != nil {
panic(err)
}
c.Auth = resp.ToAuth()
//Join server
err = c.JoinServer("localhost", 25565)
if err != nil {
panic(err)
}
//Handle game
// events := game.GetEvents()
// go game.HandleGame()
// for e := range events { //Reciving events
// switch e.(type) {
// case bot.PlayerSpawnEvent:
// fmt.Println("Player is spawned!")
// }
// }
}

14
cmd/ping/ping.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
bot "github.com/Tnze/gomcbot"
"log"
)
func main() {
resp, err := bot.PingAndList("play.miaoscraft.cn", 25565)
if err != nil {
log.Fatalf("ping and list server fail: %v", err)
}
log.Println("Status:" + resp)
}

83556
data/blocks.go Normal file

File diff suppressed because it is too large Load Diff

4324
data/en_us.go Normal file

File diff suppressed because it is too large Load Diff

2392
data/items.go Normal file

File diff suppressed because it is too large Load Diff

9
data/items_test.go Normal file
View File

@ -0,0 +1,9 @@
package data
import "testing"
func TestItemsString(t *testing.T) {
for i := 0; i < 789+1; i++ {
t.Log(Solt{ID: i, Count: byte(i%64 + 1)})
}
}

154
data/packetIDs.go Normal file
View File

@ -0,0 +1,154 @@
package data
//Clientbound packet IDs
const (
SpawnObject byte = iota //0x00
SpawnExperienceOrb
SpawnGlobalEntity
SpawnMob
SpawnPainting
SpawnPlayer
AnimationClientbound
Statistics
BlockBreakAnimation
UpdateBlockEntity
BlockAction
BlockChange
BossBar
ServerDifficulty
ChatMessageClientbound
MultiBlockChange
TabComplete //0x10
DeclareCommands
ConfirmTransaction
CloseWindow
WindowItems
WindowProperty
SetSlot
SetCooldown
PluginMessageClientbound
NamedSoundEffect
DisconnectPlay
EntityStatus
Explosion
UnloadChunk
ChangeGameState
OpenHorseWindow
KeepAliveClientbound //0x20
ChunkData
Effect
Particle
UpdateLight
JoinGame
MapData
TradeList
EntityRelativeMove
EntityLookAndRelativeMove
EntityLook
Entity
VehicleMoveClientbound
OpenBook
OpenWindow
OpenSignEditor
CraftRecipeResponse //0x30
PlayerAbilitiesClientbound
CombatEvent
PlayerInfo
FacePlayer
PlayerPositionAndLookClientbound
UnlockRecipes
DestroyEntities
RemoveEntityEffect
ResourcePackSend
Respawn
EntityHeadLook
SelectAdvancementTab
WorldBorder
Camera
HeldItemChangeClientbound
UpdateViewPosition //0x40
UpdateViewDistance
DisplayScoreboard
EntityMetadata
AttachEntity
EntityVelocity
EntityEquipment
SetExperience
UpdateHealth
ScoreboardObjective
SetPassengers
Teams
UpdateScore
SpawnPosition
TimeUpdate
Title
EntitySoundEffect //0x50
SoundEffect
StopSound
PlayerListHeaderAndFooter
NBTQueryResponse
CollectItem
EntityTeleport
Advancements
EntityProperties
EntityEffect
DeclareRecipes
Tags //0x5B
)
//Serverbound packet IDs
const (
TeleportConfirm byte = iota //0x00
QueryBlockNBT
SetDifficulty
ChatMessageServerbound
ClientStatus
ClientSettings
TabCompleteServerbound
ConfirmTransactionServerbound
ClickWindowButton
ClickWindow
CloseWindowServerbound
PluginMessageServerbound
EditBook
QueryEntityNBT
UseEntity
KeepAliveServerbound
LockDifficulty //0x10
PlayerPosition
PlayerPositionAndLookServerbound
PlayerLook
Player
VehicleMoveServerbound
SteerBoat
PickItem
CraftRecipeRequest
PlayerAbilitiesServerbound
PlayerDigging
EntityAction
SteerVehicle
RecipeBookData
NameItem
ResourcePackStatus
AdvancementTab //0x20
SelectTrade
SetBeaconEffect
HeldItemChangeServerbound
UpdateCommandBlock
UpdateCommandBlockMinecart
CreativeInventoryAction
UpdateJigsawBlock
UpdateStructureBlock
UpdateSign
AnimationServerbound
Spectate
PlayerBlockPlacement
UseItem //0x2D
)

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/Tnze/go-mc
go 1.12
require github.com/Tnze/gomcbot v0.0.0-20190428125515-3d2883562e1d

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/Tnze/gomcbot v0.0.0-20190428125515-3d2883562e1d h1:EnNxILaQhPa7kITN7A2s2xc1rd0ly+xNcGNxC9sm26g=
github.com/Tnze/gomcbot v0.0.0-20190428125515-3d2883562e1d/go.mod h1:qH9H+ek0PFB3oy6PPPwbidkHLvha/mpNeR9YxT+Qdjo=

54
net/CFB8/cfb8.go Normal file
View File

@ -0,0 +1,54 @@
//From https://play.golang.org/p/LTbId4b6M2
package CFB8
import "crypto/cipher"
type CFB8 struct {
c cipher.Block
blockSize int
iv, tmp []byte
de bool
}
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
cp := make([]byte, len(iv))
copy(cp, iv)
return &CFB8{
c: c,
blockSize: c.BlockSize(),
iv: cp,
tmp: make([]byte, c.BlockSize()),
de: true,
}
}
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
cp := make([]byte, len(iv))
copy(cp, iv)
return &CFB8{
c: c,
blockSize: c.BlockSize(),
iv: cp,
tmp: make([]byte, c.BlockSize()),
de: false,
}
}
func (cf *CFB8) XORKeyStream(dst, src []byte) {
for i := 0; i < len(src); i++ {
val := src[i]
copy(cf.tmp, cf.iv)
cf.c.Encrypt(cf.iv, cf.iv)
val = val ^ cf.iv[0]
copy(cf.iv, cf.tmp[1:])
if cf.de {
cf.iv[15] = src[i]
} else {
cf.iv[15] = val
}
dst[i] = val
}
}

57
net/conn.go Normal file
View File

@ -0,0 +1,57 @@
package net
import (
"bufio"
"crypto/cipher"
"io"
"net"
pk "github.com/Tnze/go-mc/net/packet"
)
type Conn struct {
socket net.Conn
io.ByteReader
io.Writer
threshold int
}
func DialMC(addr string) (conn *Conn, err error) {
conn = new(Conn)
conn.socket, err = net.Dial("tcp", addr)
if err != nil {
return
}
conn.ByteReader = bufio.NewReader(conn.socket)
conn.Writer = conn.socket
return
}
func (c *Conn) ReadPacket() (pk.Packet, error) {
pk, err := pk.RecvPacket(c.ByteReader, c.threshold > 0)
return *pk, err
}
func (c *Conn) WritePacket(p pk.Packet) error {
_, err := c.Write(p.Pack(c.threshold))
return err
}
func (c *Conn) SetCipher(encoStream, decoStream cipher.Stream) {
//加密连接
c.ByteReader = bufio.NewReader(cipher.StreamReader{ //Set reciver for AES
S: decoStream,
R: c.socket,
})
c.Writer = cipher.StreamWriter{
S: encoStream,
W: c.socket,
}
}
func (c *Conn) SetThreshold(t int) {
c.threshold = t
}

141
net/packet/packet.go Normal file
View File

@ -0,0 +1,141 @@
package packet
import (
"bytes"
"compress/zlib"
"fmt"
"io"
)
// Packet define a net data package
type Packet struct {
ID byte
Data []byte
}
//Marshal generate Packet with the ID and Fields
func Marshal(ID byte, fields ...FieldEncoder) (pk Packet) {
pk.ID = ID
for _, v := range fields {
pk.Data = append(pk.Data, v.Encode()...)
}
return
}
//Scan decode the packet and fill data into fields
func (p Packet) Scan(fields ...FieldDecoder) error {
r := bytes.NewReader(p.Data)
for _, v := range fields {
err := v.Decode(r)
if err != nil {
return err
}
}
return nil
}
// Pack 打包一个数据包
func (p *Packet) Pack(threshold int) (pack []byte) {
data := []byte{p.ID} //data
data = append(data, p.Data...) //data
if threshold > 0 { //是否启用了压缩
if len(data) > threshold { //是否需要压缩
Len := len(data)
VarLen := VarInt(Len).Encode()
data = Compress(data)
pack = append(pack, VarInt(len(VarLen)+len(data)).Encode()...)
pack = append(pack, VarLen...)
pack = append(pack, data...)
} else {
pack = append(pack, VarInt(int32(len(data)+1)).Encode()...)
pack = append(pack, 0x00)
pack = append(pack, data...)
}
} else {
pack = append(pack, VarInt(int32(len(data))).Encode()...) //len
pack = append(pack, data...)
}
return
}
// RecvPacket recive a packet from server
func RecvPacket(r io.ByteReader, useZlib bool) (*Packet, error) {
var len int
for i := 0; i < 5; i++ { //读数据前的长度标记
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read len of packet fail: %v", err)
}
len |= (int(b&0x7F) << uint(7*i))
if b&0x80 == 0 {
break
}
}
if len < 1 {
return nil, fmt.Errorf("packet length too short")
}
data := make([]byte, len) //读包内容
var err error
for i := 0; i < len; i++ {
data[i], err = r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read content of packet fail: %v", err)
}
}
//解压数据
if useZlib {
return UnCompress(data)
}
return &Packet{
ID: data[0],
Data: data[1:],
}, nil
}
// UnCompress 读取一个压缩的包
func UnCompress(data []byte) (*Packet, error) {
reader := bytes.NewReader(data)
var sizeUncompressed VarInt
if err := sizeUncompressed.Decode(reader); err != nil {
return nil, err
}
uncompressData := make([]byte, sizeUncompressed)
if sizeUncompressed != 0 { // != 0 means compressed, let's decompress
r, err := zlib.NewReader(reader)
if err != nil {
return nil, fmt.Errorf("decompress fail: %v", err)
}
_, err = io.ReadFull(r, uncompressData)
if err != nil {
return nil, fmt.Errorf("decompress fail: %v", err)
}
r.Close()
} else {
uncompressData = data[1:]
}
return &Packet{
ID: uncompressData[0],
Data: uncompressData[1:],
}, nil
}
// Compress 压缩数据
func Compress(data []byte) []byte {
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write(data)
w.Close()
return b.Bytes()
}

62
net/packet/packet_test.go Normal file
View File

@ -0,0 +1,62 @@
package packet
import (
"bytes"
"testing"
)
var VarInts = []VarInt{0, 1, 2, 127, 128, 255, 2147483647, -1, -2147483648}
var PackedVarInts = [][]byte{
[]byte{0x00},
[]byte{0x01},
[]byte{0x02},
[]byte{0x7f},
[]byte{0x80, 0x01},
[]byte{0xff, 0x01},
[]byte{0xff, 0xff, 0xff, 0xff, 0x07},
[]byte{0xff, 0xff, 0xff, 0xff, 0x0f},
[]byte{0x80, 0x80, 0x80, 0x80, 0x08},
}
func TestPackInt(t *testing.T) {
for i, v := range VarInts {
p := v.Encode()
if !bytes.Equal(p, PackedVarInts[i]) {
t.Errorf("pack int %d should be \"% x\", get \"% x\"", v, PackedVarInts[i], p)
}
}
}
func TestUnpackInt(t *testing.T) {
for i, v := range PackedVarInts {
var vi VarInt
if err := vi.Decode(bytes.NewReader(v)); err != nil {
t.Errorf("unpack \"% x\" error: %v", v, err)
}
if vi != VarInts[i] {
t.Errorf("unpack \"% x\" should be %d, get %d", v, VarInts[i], vi)
}
}
}
func TestPositionPack(t *testing.T) {
// x (-33554432 to 33554431), y (-2048 to 2047), z (-33554432 to 33554431)
for x := -33554432; x < 33554432; x += 55443 {
for y := -2048; y < 2048; y += 48 {
for z := -33554432; z < 33554432; z += 55443 {
var (
pos1 Position
pos2 = Position{x, y, z}
)
if err := pos1.Decode(bytes.NewReader(pos2.Encode())); err != nil {
t.Errorf("Position decode fail: %v", err)
}
if pos1 != pos2 {
t.Errorf("cannot pack %v", pos2)
}
}
}
}
}

335
net/packet/types.go Normal file
View File

@ -0,0 +1,335 @@
package packet
import (
"io"
"math"
)
type Field interface {
FieldEncoder
FieldDecoder
}
type FieldEncoder interface {
Encode() []byte
}
type FieldDecoder interface {
Decode(r io.ByteReader) error
}
type (
//Boolean of True is encoded as 0x01, false as 0x00.
Boolean bool
//Byte is signed 8-bit integer, two's complement
Byte int8
//UnsignedByte is unsigned 8-bit integer
UnsignedByte uint8
//Short is signed 16-bit integer, two's complement
Short int16
//UnsignedShort is unsigned 16-bit integer
UnsignedShort uint16
//Int is signed 32-bit integer, two's complement
Int int32
//Long is signed 64-bit integer, two's complement
Long int64
//A Float is a single-precision 32-bit IEEE 754 floating point number
Float float32
//A Double is a double-precision 64-bit IEEE 754 floating point number
Double float64
//String is sequence of Unicode scalar values
String string
//Chat is encoded as a String with max length of 32767.
Chat = String
//Identifier is encoded as a String with max length of 32767.
Identifier = String
//VarInt is variable-length data encoding a two's complement signed 32-bit integer
VarInt int32
//VarLong is variable-length data encoding a two's complement signed 64-bit integer
VarLong int64
//Position x as a 26-bit integer, followed by y as a 12-bit integer, followed by z as a 26-bit integer (all signed, two's complement)
Position struct {
X, Y, Z int
}
//Angle is rotation angle in steps of 1/256 of a full turn
Angle int8
//UUID encoded as an unsigned 128-bit integer
UUID [16]byte
)
//ReadNBytes read N bytes from bytes.Reader
func ReadNBytes(r io.ByteReader, n int) (bs []byte, err error) {
bs = make([]byte, n)
for i := 0; i < n; i++ {
bs[i], err = r.ReadByte()
if err != nil {
return
}
}
return
}
//Encode a Boolean
func (b Boolean) Encode() []byte {
if b {
return []byte{0x01}
}
return []byte{0x00}
}
//Decode a Boolean
func (b *Boolean) Decode(r io.ByteReader) error {
v, err := r.ReadByte()
if err != nil {
return err
}
*b = Boolean(v != 0)
return nil
}
// Encode a String
func (s String) Encode() (p []byte) {
byteString := []byte(s)
p = append(p, VarInt(len(byteString)).Encode()...) //len
p = append(p, byteString...) //data
return
}
//Decode a String
func (s *String) Decode(r io.ByteReader) error {
var l VarInt //String length
if err := l.Decode(r); err != nil {
return err
}
bs, err := ReadNBytes(r, int(l))
if err != nil {
return err
}
*s = String(bs)
return nil
}
//Encode a Byte
func (b Byte) Encode() []byte {
return []byte{byte(b)}
}
//Decode a Byte
func (b *Byte) Decode(r io.ByteReader) error {
v, err := r.ReadByte()
if err != nil {
return err
}
*b = Byte(v)
return nil
}
//Encode a UnsignedByte
func (ub UnsignedByte) Encode() []byte {
return []byte{byte(ub)}
}
//Decode a UnsignedByte
func (ub *UnsignedByte) Decode(r io.ByteReader) error {
v, err := r.ReadByte()
if err != nil {
return err
}
*ub = UnsignedByte(v)
return nil
}
// Encode a Signed Short
func (s Short) Encode() []byte {
n := uint16(s)
return []byte{
byte(n >> 8),
byte(n),
}
}
//Decode a Short
func (s *Short) Decode(r io.ByteReader) error {
bs, err := ReadNBytes(r, 2)
if err != nil {
return err
}
*s = Short(int16(bs[0])<<8 | int16(bs[1]))
return nil
}
// Encode a Unsigned Short
func (us UnsignedShort) Encode() []byte {
n := uint16(us)
return []byte{
byte(n >> 8),
byte(n),
}
}
//Decode a UnsignedShort
func (us *UnsignedShort) Decode(r io.ByteReader) error {
bs, err := ReadNBytes(r, 2)
if err != nil {
return err
}
*us = UnsignedShort(int16(bs[0])<<8 | int16(bs[1]))
return nil
}
// Encode a Int
func (i Int) Encode() []byte {
n := uint32(i)
return []byte{
byte(n >> 24), byte(n >> 16),
byte(n >> 8), byte(n),
}
}
//Decode a Int
func (i *Int) Decode(r io.ByteReader) error {
bs, err := ReadNBytes(r, 4)
if err != nil {
return err
}
*i = Int(int32(bs[0])<<24 | int32(bs[1])<<16 | int32(bs[2])<<8 | int32(bs[3]))
return nil
}
// Encode a Long
func (l Long) Encode() []byte {
n := uint64(l)
return []byte{
byte(n >> 56), byte(n >> 48), byte(n >> 40), byte(n >> 32),
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
}
}
//Decode a Long
func (l *Long) Decode(r io.ByteReader) error {
bs, err := ReadNBytes(r, 8)
if err != nil {
return err
}
*l = Long(int64(bs[0])<<56 | int64(bs[1])<<48 | int64(bs[2])<<40 | int64(bs[3])<<32 |
int64(bs[4])<<24 | int64(bs[5])<<16 | int64(bs[6])<<8 | int64(bs[7]))
return nil
}
//Encode a VarInt
func (v VarInt) Encode() (vi []byte) {
num := uint32(v)
for {
b := num & 0x7F
num >>= 7
if num != 0 {
b |= 0x80
}
vi = append(vi, byte(b))
if num == 0 {
break
}
}
return
}
//Decode a VarInt
func (v *VarInt) Decode(r io.ByteReader) error {
var n uint32
for i := 0; i < 5; i++ { //读数据前的长度标记
sec, err := r.ReadByte()
if err != nil {
return err
}
n |= (uint32(sec&0x7F) << uint32(7*i))
if sec&0x80 == 0 {
break
}
}
*v = VarInt(n)
return nil
}
//Encode a Position
func (p Position) Encode() []byte {
b := make([]byte, 8)
position := (uint64(p.X&0x3FFFFFF)<<38 | uint64((p.Z&0x3FFFFFF)<<12) | uint64(p.Y&0xFFF))
for i := 7; i >= 0; i-- {
b[i] = byte(position)
position >>= 8
}
return b
}
// Decode a Position
func (p *Position) Decode(r io.ByteReader) error {
var v Long
if err := v.Decode(r); err != nil {
return err
}
x := int(v >> 38)
y := int(v & 0xFFF)
z := int(v << 26 >> 38)
//处理负数
if x >= 1<<25 {
x -= 1 << 26
}
if y >= 1<<11 {
y -= 1 << 12
}
if z >= 1<<25 {
z -= 1 << 26
}
p.X, p.Y, p.Z = x, y, z
return nil
}
//Encode a Float
func (f Float) Encode() []byte {
return Int(math.Float32bits(float32(f))).Encode()
}
// Decode a Float
func (f *Float) Decode(r io.ByteReader) error {
var v Int
if err := v.Decode(r); err != nil {
return err
}
*f = Float(math.Float32frombits(uint32(v)))
return nil
}
//Encode a Double
func (d Double) Encode() []byte {
return Long(math.Float64bits(float64(d))).Encode()
}
// Decode a Double
func (d *Double) Decode(r io.ByteReader) error {
var v Long
if err := v.Decode(r); err != nil {
return err
}
*d = Double(math.Float64frombits(uint64(v)))
return nil
}