init
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"go.inferGopath": false
|
||||
}
|
104
authenticate/authenticate.go
Normal file
104
authenticate/authenticate.go
Normal 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"`
|
||||
}
|
32
authenticate/authenticate_test.go
Normal file
32
authenticate/authenticate_test.go
Normal 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
1
bot
Submodule
Submodule bot added at 1b7712dc24
105
chat/chatMsg.go
Normal file
105
chat/chatMsg.go
Normal 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
53
chat/chatMsg_test.go
Normal 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
21
cmd/daze/daze.go
Normal 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
67
cmd/examples_test.go
Normal 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
14
cmd/ping/ping.go
Normal 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
83556
data/blocks.go
Normal file
File diff suppressed because it is too large
Load Diff
4324
data/en_us.go
Normal file
4324
data/en_us.go
Normal file
File diff suppressed because it is too large
Load Diff
2392
data/items.go
Normal file
2392
data/items.go
Normal file
File diff suppressed because it is too large
Load Diff
9
data/items_test.go
Normal file
9
data/items_test.go
Normal 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
154
data/packetIDs.go
Normal 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
5
go.mod
Normal 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
2
go.sum
Normal 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
54
net/CFB8/cfb8.go
Normal 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
57
net/conn.go
Normal 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
141
net/packet/packet.go
Normal 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
62
net/packet/packet_test.go
Normal 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
335
net/packet/types.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user