├── .gitignore ├── LICENSE ├── Makefile ├── README ├── chunk.go ├── chunkymonkey.go ├── entity.go ├── game.go ├── nbt ├── Makefile └── nbt.go ├── player.go ├── proto.go └── record.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | *.a 3 | _obj 4 | chunkymonkey 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Stefan Hajnoczi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | # TODO Properly build and link packages 4 | GC += -I nbt/_obj 5 | LD += -L nbt/_obj 6 | 7 | TARG=chunkymonkey 8 | GOFILES=\ 9 | chunkymonkey.go \ 10 | proto.go \ 11 | chunk.go \ 12 | game.go \ 13 | player.go \ 14 | entity.go \ 15 | record.go \ 16 | 17 | include $(GOROOT)/src/Make.cmd 18 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ___ 2 | C(o o)O C H U N K Y M O N K E Y 3 | (.) \ 4 | w====m==| minecraft server 5 | / 6 | 7 | Chunky Monkey is a Minecraft Alpha multiplayer server. It is licensed under 8 | the MIT open source license, please see the LICENSE file for more information. 9 | 10 | Website: http://github.com/stefanha/chunkymonkey 11 | Contact: Stefan Hajnoczi 12 | 13 | Status 14 | ====== 15 | 16 | Chunky Monkey is currently in early development and does not yet run games. 17 | 18 | Requirements 19 | ============ 20 | 21 | The Go toolchain must be installed. For more information, see: 22 | http://golang.org/doc/install.html#fetch 23 | 24 | Building 25 | ======== 26 | 27 | $ cd nbt && make && cd .. 28 | $ make 29 | 30 | Running 31 | ======= 32 | 33 | Serve up a single player world: 34 | 35 | $ ./chunkymonkey ~/.minecraft/saves/World1 36 | 2010/10/03 16:32:13 Listening on :25565 37 | 38 | Record/replay 39 | ============= 40 | 41 | For debugging it is often useful to record a player's actions and replay them 42 | one or more times later. This makes it possible to simulate multiplayer games 43 | without having real people logging in. 44 | 45 | To record a session: 46 | 47 | $ ./chunkymonkey --record player.log ~/.minecraft/saves/World1 48 | 49 | To replay a session: 50 | 51 | $ ./chunkymonkey --replay player.log ~/.minecraft/saves/World1 & 52 | $ java -jar Minecraft.jar # start the first player (real human) 53 | $ nc localhost 25565 # start the second player (replay from log file) 54 | 55 | The recorded player will start when the second client connects to the server. 56 | Use netcat or a similar tool to start an otherwise idle TCP connection to the 57 | server. 58 | -------------------------------------------------------------------------------- /chunk.go: -------------------------------------------------------------------------------- 1 | // Map chunks 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "os" 9 | "log" 10 | "path" 11 | "nbt" 12 | ) 13 | 14 | const ( 15 | // Chunk coordinates can be converted to block coordinates 16 | ChunkSizeX = 16 17 | ChunkSizeY = 128 18 | ChunkSizeZ = 16 19 | 20 | // The area within which a client receives updates 21 | ChunkRadius = 10 22 | ) 23 | 24 | type ChunkCoord int32 25 | 26 | // A chunk is slice of the world map 27 | type Chunk struct { 28 | X, Z ChunkCoord 29 | Blocks []byte 30 | BlockData []byte 31 | SkyLight []byte 32 | BlockLight []byte 33 | HeightMap []byte 34 | players map[EntityID]*Player 35 | } 36 | 37 | // Convert an (x, z) block coordinate pair to chunk coordinates 38 | func BlockToChunkCoords(blockX float64, blockZ float64) (chunkX ChunkCoord, chunkZ ChunkCoord) { 39 | return ChunkCoord(blockX / ChunkSizeX), ChunkCoord(blockZ / ChunkSizeZ) 40 | } 41 | 42 | // Load a chunk from its NBT representation 43 | func loadChunk(reader io.Reader) (chunk *Chunk, err os.Error) { 44 | level, err := nbt.Read(reader) 45 | if err != nil { 46 | return 47 | } 48 | 49 | chunk = &Chunk{ 50 | X: ChunkCoord(level.Lookup("/Level/xPos").(*nbt.Int).Value), 51 | Z: ChunkCoord(level.Lookup("/Level/zPos").(*nbt.Int).Value), 52 | Blocks: level.Lookup("/Level/Blocks").(*nbt.ByteArray).Value, 53 | BlockData: level.Lookup("/Level/Data").(*nbt.ByteArray).Value, 54 | SkyLight: level.Lookup("/Level/SkyLight").(*nbt.ByteArray).Value, 55 | BlockLight: level.Lookup("/Level/BlockLight").(*nbt.ByteArray).Value, 56 | HeightMap: level.Lookup("/Level/HeightMap").(*nbt.ByteArray).Value, 57 | players: make(map[EntityID]*Player), 58 | } 59 | return 60 | } 61 | 62 | // ChunkManager contains all chunks and can look them up 63 | type ChunkManager struct { 64 | worldPath string 65 | chunks map[uint64]*Chunk 66 | } 67 | 68 | func NewChunkManager(worldPath string) *ChunkManager { 69 | return &ChunkManager{ 70 | worldPath: worldPath, 71 | chunks: make(map[uint64]*Chunk), 72 | } 73 | } 74 | 75 | func base36Encode(n int32) (s string) { 76 | alphabet := "0123456789abcdefghijklmnopqrstuvwxyz" 77 | negative := false 78 | 79 | if n < 0 { 80 | n = -n 81 | negative = true 82 | } 83 | if n == 0 { 84 | return "0" 85 | } 86 | 87 | for n != 0 { 88 | i := n % int32(len(alphabet)) 89 | n /= int32(len(alphabet)) 90 | s = string(alphabet[i:i+1]) + s 91 | } 92 | if negative { 93 | s = "-" + s 94 | } 95 | return 96 | } 97 | 98 | func (mgr *ChunkManager) chunkPath(x ChunkCoord, z ChunkCoord) string { 99 | return path.Join(mgr.worldPath, base36Encode(int32(x&63)), base36Encode(int32(z&63)), 100 | "c."+base36Encode(int32(x))+"."+base36Encode(int32(z))+".dat") 101 | } 102 | 103 | // Get a chunk at given coordinates 104 | func (mgr *ChunkManager) Get(x ChunkCoord, z ChunkCoord) (chunk *Chunk) { 105 | key := uint64(x)<<32 | uint64(uint32(z)) 106 | chunk, ok := mgr.chunks[key] 107 | if ok { 108 | return 109 | } 110 | 111 | file, err := os.Open(mgr.chunkPath(x, z), os.O_RDONLY, 0) 112 | if err != nil { 113 | log.Exit("ChunkManager.Get: ", err.String()) 114 | } 115 | 116 | chunk, err = loadChunk(file) 117 | file.Close() 118 | if err != nil { 119 | log.Exit("ChunkManager.loadChunk: ", err.String()) 120 | } 121 | 122 | mgr.chunks[key] = chunk 123 | return 124 | } 125 | 126 | // Return a channel to iterate over all chunks within a chunk's radius 127 | func (mgr *ChunkManager) ChunksInRadius(chunkX ChunkCoord, chunkZ ChunkCoord) (c chan *Chunk) { 128 | c = make(chan *Chunk) 129 | go func() { 130 | for z := chunkZ - ChunkRadius; z <= chunkZ + ChunkRadius; z++ { 131 | for x := chunkX - ChunkRadius; x <= chunkX + ChunkRadius; x++ { 132 | c <- mgr.Get(x, z) 133 | } 134 | } 135 | close(c) 136 | }() 137 | return 138 | } 139 | 140 | // Return a channel to iterate over all chunks within a player's radius 141 | func (mgr *ChunkManager) ChunksInPlayerRadius(player *Player) chan *Chunk { 142 | playerX, playerZ := BlockToChunkCoords(player.position.x, player.position.z) 143 | return mgr.ChunksInRadius(playerX, playerZ) 144 | } 145 | 146 | // Return a channel to iterate over all players within a chunk's radius 147 | func (mgr *ChunkManager) PlayersInRadius(x ChunkCoord, z ChunkCoord) (c chan *Player) { 148 | c = make(chan *Player) 149 | go func() { 150 | alreadySent := make(map[EntityID]*Player) 151 | for chunk := range mgr.ChunksInRadius(x, z) { 152 | for entityID, player := range chunk.players { 153 | if _, ok := alreadySent[entityID]; !ok { 154 | c <- player 155 | alreadySent[entityID] = player 156 | } 157 | } 158 | } 159 | close(c) 160 | }() 161 | return 162 | } 163 | 164 | // Return a channel to iterate over all players within a chunk's radius 165 | func (mgr *ChunkManager) PlayersInPlayerRadius(player *Player) chan *Player { 166 | x, z := BlockToChunkCoords(player.position.x, player.position.z) 167 | return mgr.PlayersInRadius(x, z) 168 | } 169 | 170 | // Transmit a packet to all players in radius (except the player itself) 171 | func (mgr *ChunkManager) MulticastPacket(packet []byte, sender *Player) { 172 | for receiver := range mgr.PlayersInPlayerRadius(sender) { 173 | if receiver == sender { 174 | continue 175 | } 176 | 177 | receiver.TransmitPacket(packet) 178 | } 179 | } 180 | 181 | // Add a player to the game 182 | // This function sends spawn messages to all players in range. It also spawns 183 | // all existing players so the new player can see them. 184 | func (mgr *ChunkManager) AddPlayer(player *Player) { 185 | // Add player to chunks within radius 186 | for chunk := range mgr.ChunksInPlayerRadius(player) { 187 | chunk.players[player.EntityID] = player 188 | } 189 | 190 | // Spawn new player for existing players 191 | buf := &bytes.Buffer{} 192 | WriteNamedEntitySpawn(buf, player.EntityID, player.name, &player.position, &player.orientation, player.currentItem) 193 | mgr.MulticastPacket(buf.Bytes(), player) 194 | 195 | // Spawn existing players for new player 196 | buf = &bytes.Buffer{} 197 | for existing := range mgr.PlayersInPlayerRadius(player) { 198 | if existing == player { 199 | continue 200 | } 201 | 202 | WriteNamedEntitySpawn(buf, existing.EntityID, existing.name, &existing.position, &existing.orientation, existing.currentItem) 203 | } 204 | player.TransmitPacket(buf.Bytes()) 205 | } 206 | 207 | // Remove a player from the game 208 | // This function sends destroy messages so the other players see the player 209 | // disappear. 210 | func (mgr *ChunkManager) RemovePlayer(player *Player) { 211 | // Destroy player for other players 212 | buf := &bytes.Buffer{} 213 | WriteDestroyEntity(buf, player.EntityID) 214 | mgr.MulticastPacket(buf.Bytes(), player) 215 | 216 | for chunk := range mgr.ChunksInPlayerRadius(player) { 217 | chunk.players[player.EntityID] = nil, false 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /chunkymonkey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "flag" 7 | "log" 8 | "nbt" 9 | ) 10 | 11 | // The player's starting position is loaded from level.dat for now 12 | var StartPosition XYZ 13 | 14 | func loadStartPosition(worldPath string) { 15 | file, err := os.Open(path.Join(worldPath, "level.dat"), os.O_RDONLY, 0) 16 | if err != nil { 17 | log.Exit("loadStartPosition: ", err.String()) 18 | } 19 | 20 | level, err := nbt.Read(file) 21 | file.Close() 22 | if err != nil { 23 | log.Exit("loadStartPosition: ", err.String()) 24 | } 25 | 26 | pos := level.Lookup("/Data/Player/Pos") 27 | StartPosition = XYZ{ 28 | pos.(*nbt.List).Value[0].(*nbt.Double).Value, 29 | pos.(*nbt.List).Value[1].(*nbt.Double).Value, 30 | pos.(*nbt.List).Value[2].(*nbt.Double).Value, 31 | } 32 | } 33 | 34 | func usage() { 35 | os.Stderr.WriteString("usage: " + os.Args[0] + " \n") 36 | flag.PrintDefaults() 37 | } 38 | 39 | func main() { 40 | flag.Usage = usage 41 | flag.Parse() 42 | 43 | if flag.NArg() != 1 { 44 | flag.Usage() 45 | os.Exit(1) 46 | } 47 | 48 | worldPath := flag.Arg(0) 49 | 50 | loadStartPosition(worldPath) 51 | chunkManager := NewChunkManager(worldPath) 52 | game := NewGame(chunkManager) 53 | game.Serve(":25565") 54 | } 55 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type EntityID int32 4 | 5 | type Entity struct { 6 | EntityID EntityID 7 | } 8 | 9 | type EntityManager struct { 10 | nextEntityID EntityID 11 | entities map[EntityID]*Entity 12 | } 13 | 14 | // Allocate and assign a new entity ID 15 | func (mgr *EntityManager) AddEntity(entity *Entity) { 16 | // EntityManager starts initialized to zero 17 | if mgr.entities == nil { 18 | mgr.entities = make(map[EntityID]*Entity) 19 | } 20 | 21 | // Search for next free ID 22 | entityID := mgr.nextEntityID 23 | _, exists := mgr.entities[entityID] 24 | for exists { 25 | entityID++ 26 | if entityID == mgr.nextEntityID { 27 | panic("EntityID space exhausted") 28 | } 29 | _, exists = mgr.entities[entityID] 30 | } 31 | 32 | entity.EntityID = entityID 33 | mgr.entities[entityID] = entity 34 | mgr.nextEntityID = entityID + 1 35 | } 36 | 37 | func (mgr *EntityManager) RemoveEntity(entity *Entity) { 38 | mgr.entities[entity.EntityID] = nil, false 39 | } 40 | -------------------------------------------------------------------------------- /game.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net" 7 | "time" 8 | "fmt" 9 | ) 10 | 11 | type XYZ struct { 12 | x, y, z float64 13 | } 14 | 15 | type Orientation struct { 16 | rotation float32 17 | pitch float32 18 | } 19 | 20 | type Game struct { 21 | chunkManager *ChunkManager 22 | mainQueue chan func(*Game) 23 | entityManager EntityManager 24 | players map[EntityID]*Player 25 | time int64 26 | } 27 | 28 | func (game *Game) Login(conn net.Conn) { 29 | username, err := ReadHandshake(conn) 30 | if err != nil { 31 | log.Stderr("ReadHandshake: ", err.String()) 32 | return 33 | } 34 | log.Stderr("Client ", conn.RemoteAddr(), " connected as ", username) 35 | WriteHandshake(conn, "-") 36 | 37 | _, _, err = ReadLogin(conn) 38 | if err != nil { 39 | log.Stderr("ReadLogin: ", err.String()) 40 | return 41 | } 42 | WriteLogin(conn) 43 | 44 | StartPlayer(game, conn, username) 45 | } 46 | 47 | func (game *Game) Serve(addr string) { 48 | listener, e := net.Listen("tcp", addr) 49 | if e != nil { 50 | log.Exit("Listen: ", e.String()) 51 | } 52 | log.Stderr("Listening on ", addr) 53 | 54 | for { 55 | conn, e2 := listener.Accept() 56 | if e2 != nil { 57 | log.Stderr("Accept: ", e2.String()) 58 | continue 59 | } 60 | 61 | go game.Login(WrapConn(conn)) 62 | } 63 | } 64 | 65 | func (game *Game) AddPlayer(player *Player) { 66 | game.entityManager.AddEntity(&player.Entity) 67 | game.players[player.EntityID] = player 68 | game.SendChatMessage(fmt.Sprintf("%s has joined", player.name)) 69 | game.chunkManager.AddPlayer(player) 70 | } 71 | 72 | func (game *Game) RemovePlayer(player *Player) { 73 | game.chunkManager.RemovePlayer(player) 74 | game.players[player.EntityID] = nil, false 75 | game.entityManager.RemoveEntity(&player.Entity) 76 | game.SendChatMessage(fmt.Sprintf("%s has left", player.name)) 77 | } 78 | 79 | func (game *Game) MulticastPacket(packet []byte, except *Player) { 80 | for _, player := range game.players { 81 | if player == except { 82 | continue 83 | } 84 | 85 | player.TransmitPacket(packet) 86 | } 87 | } 88 | 89 | func (game *Game) SendChatMessage(message string) { 90 | buf := &bytes.Buffer{} 91 | WriteChatMessage(buf, message) 92 | game.MulticastPacket(buf.Bytes(), nil) 93 | } 94 | 95 | func (game *Game) Enqueue(f func(*Game)) { 96 | game.mainQueue <- f 97 | } 98 | 99 | func (game *Game) mainLoop() { 100 | for { 101 | f := <-game.mainQueue 102 | f(game) 103 | } 104 | } 105 | 106 | func (game *Game) timer() { 107 | ticker := time.NewTicker(1000000000) // 1 sec 108 | for { 109 | <-ticker.C 110 | game.Enqueue(func(game *Game) { game.tick() }) 111 | } 112 | } 113 | 114 | func (game *Game) sendTimeUpdate() { 115 | buf := &bytes.Buffer{} 116 | WriteTimeUpdate(buf, game.time) 117 | game.MulticastPacket(buf.Bytes(), nil) 118 | } 119 | 120 | func (game *Game) tick() { 121 | game.time += 20 122 | game.sendTimeUpdate() 123 | } 124 | 125 | func NewGame(chunkManager *ChunkManager) (game *Game) { 126 | game = &Game{ 127 | chunkManager: chunkManager, 128 | mainQueue: make(chan func(*Game), 256), 129 | players: make(map[EntityID]*Player), 130 | } 131 | 132 | go game.mainLoop() 133 | go game.timer() 134 | return 135 | } 136 | -------------------------------------------------------------------------------- /nbt/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=nbt 4 | GOFILES=\ 5 | nbt.go 6 | 7 | include $(GOROOT)/src/Make.pkg 8 | -------------------------------------------------------------------------------- /nbt/nbt.go: -------------------------------------------------------------------------------- 1 | package nbt 2 | 3 | import ( 4 | "os" 5 | "io" 6 | "fmt" 7 | "strings" 8 | "compress/gzip" 9 | "encoding/binary" 10 | ) 11 | 12 | const ( 13 | // Tag types 14 | TagEnd = 0 15 | TagByte = 1 16 | TagShort = 2 17 | TagInt = 3 18 | TagLong = 4 19 | TagFloat = 5 20 | TagDouble = 6 21 | TagByteArray = 7 22 | TagString = 8 23 | TagList = 9 24 | TagCompound = 10 25 | TagNamed = 0x80 26 | ) 27 | 28 | type Tag interface { 29 | GetType() byte 30 | Read(io.Reader) os.Error 31 | Lookup(path string) Tag 32 | } 33 | 34 | func NewTagByType(tagType byte) (tag Tag) { 35 | switch tagType { 36 | case TagEnd: 37 | tag = new(End) 38 | case TagByte: 39 | tag = new(Byte) 40 | case TagShort: 41 | tag = new(Short) 42 | case TagInt: 43 | tag = new(Int) 44 | case TagLong: 45 | tag = new(Long) 46 | case TagFloat: 47 | tag = new(Float) 48 | case TagDouble: 49 | tag = new(Double) 50 | case TagByteArray: 51 | tag = new(ByteArray) 52 | case TagString: 53 | tag = new(String) 54 | case TagList: 55 | tag = new(List) 56 | case TagCompound: 57 | tag = new(Compound) 58 | default: 59 | panic(fmt.Sprintf("Invalid NBT tag type %#x", tagType)) 60 | } 61 | return 62 | } 63 | 64 | type End struct { 65 | } 66 | 67 | func (end *End) GetType() byte { 68 | return TagEnd 69 | } 70 | 71 | func (end *End) Read(io.Reader) os.Error { 72 | return nil 73 | } 74 | 75 | func (end *End) Lookup(path string) Tag { 76 | return nil 77 | } 78 | 79 | type NamedTag struct { 80 | name string 81 | tag Tag 82 | } 83 | 84 | func (n *NamedTag) GetType() byte { 85 | return TagNamed | n.tag.GetType() 86 | } 87 | 88 | func (n *NamedTag) Read(reader io.Reader) (err os.Error) { 89 | var tagType byte 90 | err = binary.Read(reader, binary.BigEndian, &tagType) 91 | if err != nil { 92 | return 93 | } 94 | 95 | var name String 96 | if tagType != TagEnd { 97 | err = name.Read(reader) 98 | if err != nil { 99 | return 100 | } 101 | } 102 | 103 | var value = NewTagByType(tagType) 104 | err = value.Read(reader) 105 | if err != nil { 106 | return 107 | } 108 | 109 | n.name = name.Value 110 | n.tag = value 111 | return 112 | } 113 | 114 | func (n *NamedTag) Lookup(path string) Tag { 115 | components := strings.Split(path, "/", 2) 116 | if components[0] != n.name { 117 | return nil 118 | } 119 | 120 | if len(components) == 1 { 121 | return n.tag 122 | } 123 | 124 | return n.tag.Lookup(components[1]) 125 | } 126 | 127 | type Byte struct { 128 | Value int8 129 | } 130 | 131 | func (*Byte) GetType() byte { 132 | return TagByte 133 | } 134 | 135 | func (*Byte) Lookup(path string) Tag { 136 | return nil 137 | } 138 | 139 | func (b *Byte) Read(reader io.Reader) (err os.Error) { 140 | return binary.Read(reader, binary.BigEndian, &b.Value) 141 | } 142 | 143 | type Short struct { 144 | Value int16 145 | } 146 | 147 | func (*Short) GetType() byte { 148 | return TagShort 149 | } 150 | 151 | func (s *Short) Read(reader io.Reader) (err os.Error) { 152 | return binary.Read(reader, binary.BigEndian, &s.Value) 153 | } 154 | 155 | func (*Short) Lookup(path string) Tag { 156 | return nil 157 | } 158 | 159 | type Int struct { 160 | Value int32 161 | } 162 | 163 | func (*Int) GetType() byte { 164 | return TagInt 165 | } 166 | 167 | func (i *Int) Read(reader io.Reader) (err os.Error) { 168 | return binary.Read(reader, binary.BigEndian, &i.Value) 169 | } 170 | 171 | func (*Int) Lookup(path string) Tag { 172 | return nil 173 | } 174 | 175 | type Long struct { 176 | Value int64 177 | } 178 | 179 | func (*Long) GetType() byte { 180 | return TagLong 181 | } 182 | 183 | func (l *Long) Read(reader io.Reader) (err os.Error) { 184 | return binary.Read(reader, binary.BigEndian, &l.Value) 185 | } 186 | 187 | func (*Long) Lookup(path string) Tag { 188 | return nil 189 | } 190 | 191 | type Float struct { 192 | Value float32 193 | } 194 | 195 | func (*Float) GetType() byte { 196 | return TagFloat 197 | } 198 | 199 | func (f *Float) Read(reader io.Reader) (err os.Error) { 200 | return binary.Read(reader, binary.BigEndian, &f.Value) 201 | } 202 | 203 | func (*Float) Lookup(path string) Tag { 204 | return nil 205 | } 206 | 207 | type Double struct { 208 | Value float64 209 | } 210 | 211 | func (*Double) GetType() byte { 212 | return TagDouble 213 | } 214 | 215 | func (d *Double) Read(reader io.Reader) (err os.Error) { 216 | return binary.Read(reader, binary.BigEndian, &d.Value) 217 | } 218 | 219 | func (*Double) Lookup(path string) Tag { 220 | return nil 221 | } 222 | 223 | type ByteArray struct { 224 | Value []byte 225 | } 226 | 227 | func (*ByteArray) GetType() byte { 228 | return TagByteArray 229 | } 230 | 231 | func (b *ByteArray) Read(reader io.Reader) (err os.Error) { 232 | var length Int 233 | 234 | err = length.Read(reader) 235 | if err != nil { 236 | return 237 | } 238 | 239 | bs := make([]byte, length.Value) 240 | _, err = io.ReadFull(reader, bs) 241 | if err != nil { 242 | return 243 | } 244 | 245 | b.Value = bs 246 | return 247 | } 248 | 249 | func (*ByteArray) Lookup(path string) Tag { 250 | return nil 251 | } 252 | 253 | type String struct { 254 | Value string 255 | } 256 | 257 | func (*String) GetType() byte { 258 | return TagString 259 | } 260 | 261 | func (s *String) Read(reader io.Reader) (err os.Error) { 262 | var length Short 263 | 264 | err = length.Read(reader) 265 | if err != nil { 266 | return 267 | } 268 | 269 | bs := make([]byte, length.Value) 270 | _, err = io.ReadFull(reader, bs) 271 | if err != nil { 272 | return 273 | } 274 | 275 | s.Value = string(bs) 276 | return 277 | } 278 | 279 | func (*String) Lookup(path string) Tag { 280 | return nil 281 | } 282 | 283 | type List struct { 284 | Value []Tag 285 | } 286 | 287 | func (*List) GetType() byte { 288 | return TagList 289 | } 290 | 291 | func (l *List) Read(reader io.Reader) (err os.Error) { 292 | var tagType Byte 293 | err = tagType.Read(reader) 294 | if err != nil { 295 | return 296 | } 297 | 298 | var length Int 299 | err = length.Read(reader) 300 | if err != nil { 301 | return 302 | } 303 | 304 | list := make([]Tag, length.Value) 305 | for i, _ := range list { 306 | tag := NewTagByType(byte(tagType.Value)) 307 | err = tag.Read(reader) 308 | if err != nil { 309 | return 310 | } 311 | 312 | list[i] = tag 313 | } 314 | 315 | l.Value = list 316 | return 317 | } 318 | 319 | func (*List) Lookup(path string) Tag { 320 | return nil 321 | } 322 | 323 | type Compound struct { 324 | tags map[string]*NamedTag 325 | } 326 | 327 | func (*Compound) GetType() byte { 328 | return TagCompound 329 | } 330 | 331 | func (c *Compound) Read(reader io.Reader) (err os.Error) { 332 | tags := make(map[string]*NamedTag) 333 | for { 334 | tag := &NamedTag{} 335 | err = tag.Read(reader) 336 | if err != nil { 337 | return 338 | } 339 | 340 | if tag.GetType() == TagNamed|TagEnd { 341 | break 342 | } 343 | 344 | tags[tag.name] = tag 345 | } 346 | 347 | c.tags = tags 348 | return 349 | } 350 | 351 | func (c *Compound) Lookup(path string) (tag Tag) { 352 | components := strings.Split(path, "/", 2) 353 | tag, ok := c.tags[components[0]] 354 | if !ok { 355 | return nil 356 | } 357 | 358 | return tag.Lookup(path) 359 | } 360 | 361 | func Read(reader io.Reader) (compound *NamedTag, err os.Error) { 362 | var gzipReader *gzip.Decompressor 363 | 364 | gzipReader, err = gzip.NewReader(reader) 365 | if err != nil { 366 | return 367 | } 368 | 369 | compound = &NamedTag{} 370 | err = compound.Read(gzipReader) 371 | gzipReader.Close() 372 | if err != nil { 373 | return 374 | } 375 | 376 | if compound.GetType() != TagNamed|TagCompound { 377 | return nil, os.NewError("Expected named compound tag") 378 | } 379 | return 380 | } 381 | -------------------------------------------------------------------------------- /player.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "io" 6 | "log" 7 | "net" 8 | "math" 9 | "bytes" 10 | ) 11 | 12 | type Player struct { 13 | Entity 14 | game *Game 15 | conn net.Conn 16 | name string 17 | position XYZ 18 | orientation Orientation 19 | currentItem int16 20 | txQueue chan []byte 21 | } 22 | 23 | func StartPlayer(game *Game, conn net.Conn, name string) { 24 | player := &Player{ 25 | game: game, 26 | conn: conn, 27 | name: name, 28 | position: StartPosition, 29 | orientation: Orientation{0, 0}, 30 | txQueue: make(chan []byte, 128), 31 | } 32 | 33 | go player.ReceiveLoop() 34 | go player.TransmitLoop() 35 | 36 | game.Enqueue(func(game *Game) { 37 | game.AddPlayer(player) 38 | player.postLogin() 39 | }) 40 | } 41 | 42 | func (player *Player) PacketKeepAlive() { 43 | } 44 | 45 | func (player *Player) PacketChatMessage(message string) { 46 | log.Stderrf("PacketChatMessage message=%s", message) 47 | 48 | player.game.Enqueue(func(game *Game) { game.SendChatMessage(message) }) 49 | } 50 | 51 | func (player *Player) PacketFlying(flying bool) { 52 | } 53 | 54 | func (player *Player) PacketPlayerPosition(position *XYZ, stance float64, flying bool) { 55 | log.Stderrf("PacketPlayerPosition position=(%.2f, %.2f, %.2f) stance=%.2f flying=%v", 56 | position.x, position.y, position.z, stance, flying) 57 | 58 | player.game.Enqueue(func(game *Game) { 59 | var delta = XYZ{position.x - player.position.x, 60 | position.y - player.position.y, 61 | position.z - player.position.z} 62 | distance := math.Sqrt(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z) 63 | if distance > 10 { 64 | log.Stderrf("Discarding player position that is too far removed (%.2f, %.2f, %.2f)", 65 | position.x, position.y, position.z) 66 | return 67 | } 68 | 69 | player.position = *position 70 | 71 | buf := &bytes.Buffer{} 72 | WriteEntityTeleport(buf, player.EntityID, &player.position, &player.orientation) 73 | game.MulticastPacket(buf.Bytes(), player) 74 | }) 75 | } 76 | 77 | func (player *Player) PacketPlayerLook(orientation *Orientation, flying bool) { 78 | player.game.Enqueue(func(game *Game) { 79 | // TODO input validation 80 | player.orientation = *orientation 81 | 82 | buf := &bytes.Buffer{} 83 | WriteEntityLook(buf, player.EntityID, orientation) 84 | game.MulticastPacket(buf.Bytes(), player) 85 | }) 86 | } 87 | 88 | func (player *Player) PacketPlayerDigging(status byte, x int32, y byte, z int32, face byte) { 89 | log.Stderrf("PacketPlayerDigging status=%d x=%d y=%d z=%d face=%d", 90 | status, x, y, z, face) 91 | } 92 | 93 | func (player *Player) PacketPlayerBlockPlacement(blockItemID int16, x int32, y byte, z int32, direction byte) { 94 | log.Stderrf("PacketPlayerBlockPlacement blockItemID=%d x=%d y=%d z=%d direction=%d", 95 | blockItemID, x, y, z, direction) 96 | } 97 | 98 | func (player *Player) PacketHoldingChange(blockItemID int16) { 99 | log.Stderrf("PacketHoldingChange blockItemID=%d", blockItemID) 100 | } 101 | 102 | func (player *Player) PacketArmAnimation(forward bool) { 103 | log.Stderrf("PacketArmAnimation forward=%v", forward) 104 | } 105 | 106 | func (player *Player) PacketDisconnect(reason string) { 107 | log.Stderrf("PacketDisconnect reason=%s", reason) 108 | player.game.Enqueue(func(game *Game) { 109 | game.RemovePlayer(player) 110 | close(player.txQueue) 111 | player.conn.Close() 112 | }) 113 | } 114 | 115 | func (player *Player) ReceiveLoop() { 116 | for { 117 | err := ReadPacket(player.conn, player) 118 | if err != nil { 119 | if err != os.EOF { 120 | log.Stderr("ReceiveLoop failed: ", err.String()) 121 | } 122 | return 123 | } 124 | } 125 | } 126 | 127 | func (player *Player) TransmitLoop() { 128 | for { 129 | bs := <-player.txQueue 130 | if bs == nil { 131 | return // txQueue closed 132 | } 133 | 134 | _, err := player.conn.Write(bs) 135 | if err != nil { 136 | if err != os.EOF { 137 | log.Stderr("TransmitLoop failed: ", err.String()) 138 | } 139 | return 140 | } 141 | } 142 | } 143 | 144 | func (player *Player) sendChunks(writer io.Writer) { 145 | playerX := ChunkCoord(player.position.x / ChunkSizeX) 146 | playerZ := ChunkCoord(player.position.z / ChunkSizeZ) 147 | 148 | for z := playerZ - ChunkRadius; z <= playerZ+ChunkRadius; z++ { 149 | for x := playerX - ChunkRadius; x <= playerX+ChunkRadius; x++ { 150 | WritePreChunk(writer, x, z, true) 151 | } 152 | } 153 | 154 | for z := playerZ - ChunkRadius; z <= playerZ+ChunkRadius; z++ { 155 | for x := playerX - ChunkRadius; x <= playerX+ChunkRadius; x++ { 156 | chunk := player.game.chunkManager.Get(x, z) 157 | WriteMapChunk(writer, chunk) 158 | } 159 | } 160 | } 161 | 162 | func (player *Player) TransmitPacket(packet []byte) { 163 | if packet == nil { 164 | return // skip empty packets 165 | } 166 | player.txQueue <- packet 167 | } 168 | 169 | func (player *Player) postLogin() { 170 | buf := &bytes.Buffer{} 171 | WriteSpawnPosition(buf, &player.position) 172 | player.sendChunks(buf) 173 | WritePlayerInventory(buf) 174 | WritePlayerPositionLook(buf, &player.position, &player.orientation, 175 | 0, false) 176 | player.TransmitPacket(buf.Bytes()) 177 | } 178 | -------------------------------------------------------------------------------- /proto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "fmt" 7 | "bytes" 8 | "encoding/binary" 9 | "compress/zlib" 10 | ) 11 | 12 | const ( 13 | // Sometimes it is useful to convert block coordinates to pixels 14 | PixelsPerBlock = 32 15 | 16 | // Currently only this protocol version is supported 17 | protocolVersion = 2 18 | 19 | // Packet type IDs 20 | packetIDKeepAlive = 0x0 21 | packetIDLogin = 0x1 22 | packetIDHandshake = 0x2 23 | packetIDChatMessage = 0x3 24 | packetIDTimeUpdate = 0x4 25 | packetIDPlayerInventory = 0x5 26 | packetIDSpawnPosition = 0x6 27 | packetIDFlying = 0xa 28 | packetIDPlayerPosition = 0xb 29 | packetIDPlayerLook = 0xc 30 | packetIDPlayerPositionLook = 0xd 31 | packetIDPlayerDigging = 0xe 32 | packetIDPlayerBlockPlacement = 0xf 33 | packetIDHoldingChange = 0x10 34 | packetIDArmAnimation = 0x12 35 | packetIDNamedEntitySpawn = 0x14 36 | packetIDDestroyEntity = 0x1d 37 | packetIDEntityLook = 0x20 38 | packetIDEntityTeleport = 0x22 39 | packetIDPreChunk = 0x32 40 | packetIDMapChunk = 0x33 41 | packetIDDisconnect = 0xff 42 | 43 | // Inventory types 44 | inventoryTypeMain = -1 45 | inventoryTypeArmor = -2 46 | inventoryTypeCrafting = -3 47 | ) 48 | 49 | // Callers must implement this interface to receive packets 50 | type PacketHandler interface { 51 | PacketKeepAlive() 52 | PacketChatMessage(message string) 53 | PacketFlying(flying bool) 54 | PacketPlayerPosition(position *XYZ, stance float64, flying bool) 55 | PacketPlayerLook(orientation *Orientation, flying bool) 56 | PacketPlayerDigging(status byte, x int32, y byte, z int32, face byte) 57 | PacketPlayerBlockPlacement(blockItemID int16, x int32, y byte, z int32, direction byte) 58 | PacketHoldingChange(blockItemID int16) 59 | PacketArmAnimation(forward bool) 60 | PacketDisconnect(reason string) 61 | } 62 | 63 | func boolToByte(b bool) byte { 64 | if b { 65 | return 1 66 | } 67 | return 0 68 | } 69 | 70 | func byteToBool(b byte) bool { 71 | return b != 0 72 | } 73 | 74 | func ReadString(reader io.Reader) (s string, err os.Error) { 75 | var length int16 76 | err = binary.Read(reader, binary.BigEndian, &length) 77 | if err != nil { 78 | return 79 | } 80 | 81 | bs := make([]byte, uint16(length)) 82 | _, err = io.ReadFull(reader, bs) 83 | return string(bs), err 84 | } 85 | 86 | func WriteString(writer io.Writer, s string) (err os.Error) { 87 | bs := []byte(s) 88 | 89 | err = binary.Write(writer, binary.BigEndian, int16(len(bs))) 90 | if err != nil { 91 | return 92 | } 93 | 94 | _, err = writer.Write(bs) 95 | return 96 | } 97 | 98 | func ReadHandshake(reader io.Reader) (username string, err os.Error) { 99 | var packetID byte 100 | err = binary.Read(reader, binary.BigEndian, &packetID) 101 | if err != nil { 102 | return 103 | } 104 | if packetID != packetIDHandshake { 105 | panic(fmt.Sprintf("ReadHandshake: invalid packet ID %#x", packetID)) 106 | } 107 | 108 | return ReadString(reader) 109 | } 110 | 111 | func WriteHandshake(writer io.Writer, reply string) (err os.Error) { 112 | err = binary.Write(writer, binary.BigEndian, byte(packetIDHandshake)) 113 | if err != nil { 114 | return 115 | } 116 | 117 | return WriteString(writer, reply) 118 | } 119 | 120 | func ReadLogin(reader io.Reader) (username, password string, err os.Error) { 121 | var packet struct { 122 | PacketID byte 123 | Version int32 124 | } 125 | 126 | err = binary.Read(reader, binary.BigEndian, &packet) 127 | if err != nil { 128 | return 129 | } 130 | if packet.PacketID != packetIDLogin { 131 | panic(fmt.Sprintf("ReadLogin: invalid packet ID %#x", packet.PacketID)) 132 | } 133 | if packet.Version != protocolVersion { 134 | panic(fmt.Sprintf("ReadLogin: unsupported protocol version %#x", packet.Version)) 135 | } 136 | 137 | username, err = ReadString(reader) 138 | if err != nil { 139 | return 140 | } 141 | 142 | password, err = ReadString(reader) 143 | return 144 | } 145 | 146 | func WriteLogin(writer io.Writer) (err os.Error) { 147 | _, err = writer.Write([]byte{packetIDLogin, 0, 0, 0, 0, 0, 0, 0, 0}) 148 | return err 149 | } 150 | 151 | func WriteSpawnPosition(writer io.Writer, position *XYZ) os.Error { 152 | var packet = struct { 153 | PacketID byte 154 | X int32 155 | Y int32 156 | Z int32 157 | }{ 158 | packetIDSpawnPosition, 159 | int32(position.x), 160 | int32(position.y), 161 | int32(position.z), 162 | } 163 | return binary.Write(writer, binary.BigEndian, &packet) 164 | } 165 | 166 | func WriteTimeUpdate(writer io.Writer, time int64) os.Error { 167 | var packet = struct { 168 | PacketID byte 169 | Time int64 170 | }{ 171 | packetIDTimeUpdate, 172 | time, 173 | } 174 | return binary.Write(writer, binary.BigEndian, &packet) 175 | } 176 | 177 | func WritePlayerInventory(writer io.Writer) (err os.Error) { 178 | type InventoryType struct { 179 | inventoryType int32 180 | count int16 181 | } 182 | var inventories = []InventoryType{ 183 | InventoryType{inventoryTypeMain, 36}, 184 | InventoryType{inventoryTypeArmor, 4}, 185 | InventoryType{inventoryTypeCrafting, 4}, 186 | } 187 | 188 | for _, inventory := range inventories { 189 | var packet = struct { 190 | PacketID byte 191 | InventoryType int32 192 | Count int16 193 | }{ 194 | packetIDPlayerInventory, 195 | inventory.inventoryType, 196 | inventory.count, 197 | } 198 | err = binary.Write(writer, binary.BigEndian, &packet) 199 | if err != nil { 200 | return 201 | } 202 | 203 | for i := int16(0); i < inventory.count; i++ { 204 | err = binary.Write(writer, binary.BigEndian, int16(-1)) 205 | if err != nil { 206 | return 207 | } 208 | } 209 | } 210 | return 211 | } 212 | 213 | func WritePlayerPosition(writer io.Writer, position *XYZ, stance float64, flying bool) os.Error { 214 | var packet = struct { 215 | PacketID byte 216 | X float64 217 | Y float64 218 | Stance float64 219 | Z float64 220 | Flying byte 221 | }{ 222 | packetIDPlayerPosition, 223 | position.x, 224 | position.y, 225 | stance, 226 | position.z, 227 | boolToByte(flying), 228 | } 229 | return binary.Write(writer, binary.BigEndian, &packet) 230 | } 231 | 232 | func WritePlayerPositionLook(writer io.Writer, position *XYZ, orientation *Orientation, stance float64, flying bool) os.Error { 233 | var packet = struct { 234 | PacketID byte 235 | X float64 236 | Y float64 237 | Stance float64 238 | Z float64 239 | Rotation float32 240 | Pitch float32 241 | Flying byte 242 | }{ 243 | packetIDPlayerPositionLook, 244 | position.x, 245 | position.y, 246 | stance, 247 | position.z, 248 | orientation.rotation, 249 | orientation.pitch, 250 | boolToByte(flying), 251 | } 252 | return binary.Write(writer, binary.BigEndian, &packet) 253 | } 254 | 255 | func WriteEntityLook(writer io.Writer, entityID EntityID, orientation *Orientation) os.Error { 256 | var packet = struct { 257 | PacketID byte 258 | EntityID int32 259 | Rotation byte 260 | Pitch byte 261 | }{ 262 | packetIDEntityLook, 263 | int32(entityID), 264 | byte(orientation.rotation * 256 / 360), 265 | byte(orientation.pitch * 64 / 90), 266 | } 267 | return binary.Write(writer, binary.BigEndian, &packet) 268 | } 269 | 270 | func WriteEntityTeleport(writer io.Writer, entityID EntityID, position *XYZ, orientation *Orientation) os.Error { 271 | var packet = struct { 272 | PacketID byte 273 | EntityID int32 274 | X int32 275 | Y int32 276 | Z int32 277 | Rotation byte 278 | Pitch byte 279 | }{ 280 | packetIDEntityTeleport, 281 | int32(entityID), 282 | int32(position.x * PixelsPerBlock), 283 | int32(position.y * PixelsPerBlock), 284 | int32(position.z * PixelsPerBlock), 285 | byte(orientation.rotation * 256 / 360), 286 | byte(orientation.pitch * 64 / 90), 287 | } 288 | return binary.Write(writer, binary.BigEndian, &packet) 289 | } 290 | 291 | func WritePreChunk(writer io.Writer, x ChunkCoord, z ChunkCoord, willSend bool) os.Error { 292 | var packet = struct { 293 | PacketID byte 294 | X int32 295 | Z int32 296 | WillSend byte 297 | }{ 298 | packetIDPreChunk, 299 | int32(x), 300 | int32(z), 301 | boolToByte(willSend), 302 | } 303 | return binary.Write(writer, binary.BigEndian, &packet) 304 | } 305 | 306 | func WriteMapChunk(writer io.Writer, chunk *Chunk) (err os.Error) { 307 | buf := &bytes.Buffer{} 308 | compressed, err := zlib.NewWriter(buf) 309 | if err != nil { 310 | return 311 | } 312 | 313 | compressed.Write(chunk.Blocks) 314 | compressed.Write(chunk.BlockData) 315 | compressed.Write(chunk.BlockLight) 316 | compressed.Write(chunk.SkyLight) 317 | compressed.Close() 318 | bs := buf.Bytes() 319 | 320 | var packet = struct { 321 | PacketID byte 322 | X int32 323 | Y int16 324 | Z int32 325 | SizeX byte 326 | SizeY byte 327 | SizeZ byte 328 | CompressedLength int32 329 | }{ 330 | packetIDMapChunk, 331 | int32(chunk.X * ChunkSizeX), 332 | 0, 333 | int32(chunk.Z * ChunkSizeZ), 334 | ChunkSizeX - 1, 335 | ChunkSizeY - 1, 336 | ChunkSizeZ - 1, 337 | int32(len(bs)), 338 | } 339 | 340 | err = binary.Write(writer, binary.BigEndian, &packet) 341 | if err != nil { 342 | return 343 | } 344 | err = binary.Write(writer, binary.BigEndian, bs) 345 | return 346 | } 347 | 348 | func WriteNamedEntitySpawn(writer io.Writer, entityID EntityID, name string, position *XYZ, orientation *Orientation, currentItem int16) (err os.Error) { 349 | var packetStart = struct { 350 | PacketID byte 351 | EntityID int32 352 | }{ 353 | packetIDNamedEntitySpawn, 354 | int32(entityID), 355 | } 356 | 357 | err = binary.Write(writer, binary.BigEndian, &packetStart) 358 | if err != nil { 359 | return 360 | } 361 | 362 | err = WriteString(writer, name) 363 | if err != nil { 364 | return 365 | } 366 | 367 | var packetFinish = struct { 368 | X int32 369 | Y int32 370 | Z int32 371 | Rotation byte 372 | Pitch byte 373 | CurrentItem int16 374 | }{ 375 | int32(position.x * PixelsPerBlock), 376 | int32(position.y * PixelsPerBlock), 377 | int32(position.z * PixelsPerBlock), 378 | byte(orientation.rotation), 379 | byte(orientation.pitch), 380 | currentItem, 381 | } 382 | 383 | err = binary.Write(writer, binary.BigEndian, &packetFinish) 384 | return 385 | } 386 | 387 | func WriteDestroyEntity(writer io.Writer, entityID EntityID) os.Error { 388 | var packet = struct { 389 | PacketID byte 390 | EntityID int32 391 | }{ 392 | packetIDDestroyEntity, 393 | int32(entityID), 394 | } 395 | return binary.Write(writer, binary.BigEndian, &packet) 396 | } 397 | 398 | func ReadKeepAlive(reader io.Reader, handler PacketHandler) (err os.Error) { 399 | handler.PacketKeepAlive() 400 | return 401 | } 402 | 403 | func ReadChatMessage(reader io.Reader, handler PacketHandler) (err os.Error) { 404 | var length int16 405 | err = binary.Read(reader, binary.BigEndian, &length) 406 | if err != nil { 407 | return 408 | } 409 | 410 | bs := make([]byte, length) 411 | _, err = io.ReadFull(reader, bs) 412 | if err != nil { 413 | return 414 | } 415 | 416 | // TODO sanitize chat message 417 | 418 | handler.PacketChatMessage(string(bs)) 419 | return 420 | } 421 | 422 | func WriteChatMessage(writer io.Writer, message string) (err os.Error) { 423 | err = binary.Write(writer, binary.BigEndian, byte(packetIDChatMessage)) 424 | if err != nil { 425 | return 426 | } 427 | 428 | err = WriteString(writer, message) 429 | return 430 | } 431 | 432 | func ReadFlying(reader io.Reader, handler PacketHandler) (err os.Error) { 433 | var packet struct { 434 | Flying byte 435 | } 436 | 437 | err = binary.Read(reader, binary.BigEndian, &packet) 438 | if err != nil { 439 | return 440 | } 441 | 442 | handler.PacketFlying(byteToBool(packet.Flying)) 443 | return 444 | } 445 | 446 | func ReadPlayerPosition(reader io.Reader, handler PacketHandler) (err os.Error) { 447 | var packet struct { 448 | X float64 449 | Y float64 450 | Stance float64 451 | Z float64 452 | Flying byte 453 | } 454 | 455 | err = binary.Read(reader, binary.BigEndian, &packet) 456 | if err != nil { 457 | return 458 | } 459 | 460 | handler.PacketPlayerPosition(&XYZ{packet.X, packet.Y, packet.Z}, packet.Stance, byteToBool(packet.Flying)) 461 | return 462 | } 463 | 464 | func ReadPlayerLook(reader io.Reader, handler PacketHandler) (err os.Error) { 465 | var packet struct { 466 | Rotation float32 467 | Pitch float32 468 | Flying byte 469 | } 470 | 471 | err = binary.Read(reader, binary.BigEndian, &packet) 472 | if err != nil { 473 | return 474 | } 475 | 476 | handler.PacketPlayerLook(&Orientation{packet.Rotation, packet.Pitch}, byteToBool(packet.Flying)) 477 | return 478 | } 479 | 480 | func ReadPlayerPositionLook(reader io.Reader, handler PacketHandler) (err os.Error) { 481 | var packet struct { 482 | X float64 483 | Y float64 484 | Stance float64 485 | Z float64 486 | Rotation float32 487 | Pitch float32 488 | Flying byte 489 | } 490 | 491 | err = binary.Read(reader, binary.BigEndian, &packet) 492 | if err != nil { 493 | return 494 | } 495 | 496 | handler.PacketPlayerPosition(&XYZ{packet.X, packet.Y, packet.Z}, packet.Stance, byteToBool(packet.Flying)) 497 | handler.PacketPlayerLook(&Orientation{packet.Rotation, packet.Pitch}, byteToBool(packet.Flying)) 498 | return 499 | } 500 | 501 | func ReadPlayerDigging(reader io.Reader, handler PacketHandler) (err os.Error) { 502 | var packet struct { 503 | Status byte 504 | X int32 505 | Y byte 506 | Z int32 507 | Face byte 508 | } 509 | 510 | err = binary.Read(reader, binary.BigEndian, &packet) 511 | if err != nil { 512 | return 513 | } 514 | 515 | handler.PacketPlayerDigging(packet.Status, packet.X, packet.Y, packet.Z, packet.Face) 516 | return 517 | } 518 | 519 | func ReadPlayerBlockPlacement(reader io.Reader, handler PacketHandler) (err os.Error) { 520 | var packet struct { 521 | ID int16 522 | X int32 523 | Y byte 524 | Z int32 525 | Direction byte 526 | } 527 | 528 | err = binary.Read(reader, binary.BigEndian, &packet) 529 | if err != nil { 530 | return 531 | } 532 | 533 | handler.PacketPlayerBlockPlacement(packet.ID, packet.X, packet.Y, packet.Z, packet.Direction) 534 | return 535 | } 536 | 537 | func ReadHoldingChange(reader io.Reader, handler PacketHandler) (err os.Error) { 538 | var packet struct { 539 | EntityID int32 540 | BlockItemID int16 541 | } 542 | 543 | err = binary.Read(reader, binary.BigEndian, &packet) 544 | if err != nil { 545 | return 546 | } 547 | 548 | handler.PacketHoldingChange(packet.BlockItemID) 549 | return 550 | } 551 | 552 | func ReadArmAnimation(reader io.Reader, handler PacketHandler) (err os.Error) { 553 | var packet struct { 554 | EntityID int32 555 | Forward byte 556 | } 557 | 558 | err = binary.Read(reader, binary.BigEndian, &packet) 559 | if err != nil { 560 | return 561 | } 562 | 563 | handler.PacketArmAnimation(byteToBool(packet.Forward)) 564 | return 565 | } 566 | 567 | func ReadDisconnect(reader io.Reader, handler PacketHandler) (err os.Error) { 568 | reason, err := ReadString(reader) 569 | if err != nil { 570 | return 571 | } 572 | 573 | handler.PacketDisconnect(reason) 574 | return 575 | } 576 | 577 | // Packet reader functions 578 | var readFns = map[byte]func(io.Reader, PacketHandler) os.Error{ 579 | packetIDKeepAlive: ReadKeepAlive, 580 | packetIDChatMessage: ReadChatMessage, 581 | packetIDFlying: ReadFlying, 582 | packetIDPlayerPosition: ReadPlayerPosition, 583 | packetIDPlayerLook: ReadPlayerLook, 584 | packetIDPlayerPositionLook: ReadPlayerPositionLook, 585 | packetIDPlayerDigging: ReadPlayerDigging, 586 | packetIDPlayerBlockPlacement: ReadPlayerBlockPlacement, 587 | packetIDHoldingChange: ReadHoldingChange, 588 | packetIDArmAnimation: ReadArmAnimation, 589 | packetIDDisconnect: ReadDisconnect, 590 | } 591 | 592 | func ReadPacket(reader io.Reader, handler PacketHandler) (err os.Error) { 593 | var packetID byte 594 | 595 | err = binary.Read(reader, binary.BigEndian, &packetID) 596 | if err != nil { 597 | return err 598 | } 599 | 600 | fn, ok := readFns[packetID] 601 | if !ok { 602 | return os.NewError(fmt.Sprintf("unhandled packet type %#x", packetID)) 603 | } 604 | 605 | err = fn(reader, handler) 606 | return 607 | } 608 | -------------------------------------------------------------------------------- /record.go: -------------------------------------------------------------------------------- 1 | // Wrapper for net.Conn which supports recording and replaying received data 2 | 3 | package main 4 | 5 | import ( 6 | "io" 7 | "os" 8 | "net" 9 | "log" 10 | "flag" 11 | "time" 12 | "encoding/binary" 13 | ) 14 | 15 | // Log record header 16 | type header struct { 17 | Timestamp int64 // delay since last packet, in nanoseconds 18 | Length int32 // length of data bytes 19 | } 20 | 21 | type recorder struct { 22 | conn net.Conn 23 | log io.WriteCloser 24 | lastTimestamp int64 25 | } 26 | 27 | func (recorder *recorder) Read(b []byte) (n int, err os.Error) { 28 | n, err = recorder.conn.Read(b) 29 | if err == nil { 30 | now := time.Nanoseconds() 31 | binary.Write(recorder.log, binary.BigEndian, &header{ 32 | now - recorder.lastTimestamp, 33 | int32(n), 34 | }) 35 | binary.Write(recorder.log, binary.BigEndian, b[:n]) 36 | 37 | recorder.lastTimestamp = now 38 | } 39 | return 40 | } 41 | 42 | func (recorder *recorder) Write(b []byte) (n int, err os.Error) { 43 | return recorder.conn.Write(b) 44 | } 45 | 46 | func (recorder *recorder) Close() os.Error { 47 | recorder.log.Close() 48 | return recorder.conn.Close() 49 | } 50 | 51 | func (recorder *recorder) LocalAddr() net.Addr { 52 | return recorder.conn.LocalAddr() 53 | } 54 | 55 | func (recorder *recorder) RemoteAddr() net.Addr { 56 | return recorder.conn.RemoteAddr() 57 | } 58 | 59 | func (recorder *recorder) SetTimeout(nsec int64) os.Error { 60 | return recorder.conn.SetTimeout(nsec) 61 | } 62 | 63 | func (recorder *recorder) SetReadTimeout(nsec int64) os.Error { 64 | return recorder.conn.SetReadTimeout(nsec) 65 | } 66 | 67 | func (recorder *recorder) SetWriteTimeout(nsec int64) os.Error { 68 | return recorder.conn.SetWriteTimeout(nsec) 69 | } 70 | 71 | type replayer struct { 72 | conn net.Conn 73 | log io.ReadCloser 74 | lastTimestamp int64 75 | } 76 | 77 | func (replayer *replayer) Read(b []byte) (n int, err os.Error) { 78 | var header header 79 | 80 | err = binary.Read(replayer.log, binary.BigEndian, &header) 81 | if err != nil { 82 | return 0, err 83 | } 84 | 85 | if int32(len(b)) < header.Length { 86 | return 0, os.NewError("replay read length too small") 87 | } 88 | 89 | // Wait until recorded time has passed 90 | now := time.Nanoseconds() 91 | delta := now - replayer.lastTimestamp 92 | if delta < header.Timestamp { 93 | time.Sleep(header.Timestamp - delta) 94 | } 95 | replayer.lastTimestamp = now 96 | 97 | return replayer.log.Read(b[:header.Length]) 98 | } 99 | 100 | func (replayer *replayer) Write(b []byte) (n int, err os.Error) { 101 | return replayer.conn.Write(b) 102 | } 103 | 104 | func (replayer *replayer) Close() os.Error { 105 | replayer.log.Close() 106 | return replayer.conn.Close() 107 | } 108 | 109 | func (replayer *replayer) LocalAddr() net.Addr { 110 | return replayer.conn.LocalAddr() 111 | } 112 | 113 | func (replayer *replayer) RemoteAddr() net.Addr { 114 | return replayer.conn.RemoteAddr() 115 | } 116 | 117 | func (replayer *replayer) SetTimeout(nsec int64) os.Error { 118 | return replayer.conn.SetTimeout(nsec) 119 | } 120 | 121 | func (replayer *replayer) SetReadTimeout(nsec int64) os.Error { 122 | return replayer.conn.SetReadTimeout(nsec) 123 | } 124 | 125 | func (replayer *replayer) SetWriteTimeout(nsec int64) os.Error { 126 | return replayer.conn.SetWriteTimeout(nsec) 127 | } 128 | 129 | var record = flag.String("record", "", "record received packets to file") 130 | var replay = flag.String("replay", "", "replay received packets from file") 131 | var connections = 0 132 | 133 | // Interpose a recorder or replayer onto a network connection 134 | func WrapConn(raw net.Conn) (wrapped net.Conn) { 135 | if *record != "" { 136 | file, err := os.Open(*record, os.O_CREAT|os.O_TRUNC|os.O_WRONLY, 0644) 137 | if err != nil { 138 | log.Exit("WrapConn: ", err.String()) 139 | } 140 | 141 | return &recorder{raw, file, time.Nanoseconds()} 142 | } 143 | 144 | // The second client connection will replay the log file 145 | if connections == 1 && *replay != "" { 146 | file, err := os.Open(*replay, os.O_RDONLY, 0) 147 | if err != nil { 148 | log.Exit("WrapConn: ", err.String()) 149 | } 150 | 151 | return &replayer{raw, file, time.Nanoseconds()} 152 | } 153 | connections++ 154 | 155 | return raw 156 | } 157 | --------------------------------------------------------------------------------