init
This commit is contained in:
387
main.go
Normal file
387
main.go
Normal file
@@ -0,0 +1,387 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HOST = "127.0.0.2"
|
||||
PORT = "25565"
|
||||
TYPE = "tcp"
|
||||
ProtocolVersion = 767
|
||||
VERSION = "1.21.1"
|
||||
CompressionThreshold = -1
|
||||
)
|
||||
|
||||
var maxPlayers int32
|
||||
var icon string
|
||||
var serverID = make([]byte, 20)
|
||||
|
||||
var serverPrivateKey *rsa.PrivateKey
|
||||
var serverPublicKey *rsa.PublicKey
|
||||
|
||||
var usernameRegex = regexp.MustCompile("^[a-zA-Z0-9_]{3,16}$")
|
||||
|
||||
var world World
|
||||
|
||||
func readIcon(fileName string) (icon string) {
|
||||
iconData, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
icon = "data:image/png;base64," + base64.StdEncoding.EncodeToString(iconData)
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
maxPlayers = 200
|
||||
icon = readIcon("icon.png")
|
||||
|
||||
world = World{
|
||||
Difficulty: 0,
|
||||
DifficultyLocked: false,
|
||||
Hardcore: false,
|
||||
Dimensions: []Dimension{
|
||||
{
|
||||
ID: 0,
|
||||
Name: "minecraft:overworld",
|
||||
}, {
|
||||
ID: 1,
|
||||
Name: "minecraft:the_nether",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "minecraft:the_end",
|
||||
},
|
||||
},
|
||||
ViewDistance: 4,
|
||||
SimulationDistance: 4,
|
||||
ReducedDebugInfo: false,
|
||||
EnableRespawnScreen: true,
|
||||
DoLimitedCrafting: false,
|
||||
Seed: uuid.New(),
|
||||
FeatureFlags: []string{"minecraft:vanilla", "minecraft:bundle", "minecraft:trade_rebalance"},
|
||||
DataPacks: []DatapackInfo{{
|
||||
Namespace: "minecraft",
|
||||
ID: "core",
|
||||
Version: "1.21",
|
||||
}},
|
||||
ReportDetails: map[string]string{"I hate myself": "YES, REALLY"},
|
||||
ServerLinks: []ServerLink{{
|
||||
LabelString: "",
|
||||
LabelInt: 0,
|
||||
URL: "https://brn.systems",
|
||||
}},
|
||||
}
|
||||
|
||||
var err error
|
||||
//serverID, err = serverIDCreate()
|
||||
serverID = []byte("")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
serverPrivateKey, serverPublicKey, err = GenerateRSAKeyPair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
listen, err := net.Listen(TYPE, HOST+":"+PORT)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// close listener
|
||||
defer func(listen net.Listener) {
|
||||
err := listen.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}(listen)
|
||||
|
||||
for {
|
||||
conn, err := listen.Accept()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go handleRequest(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleRequest(conn net.Conn) {
|
||||
var errOut error
|
||||
var player = Player{
|
||||
state: 0,
|
||||
conn: conn,
|
||||
name: "",
|
||||
version: 0,
|
||||
requestedAddress: "",
|
||||
requestedPort: 0,
|
||||
reader: bufio.NewReader(conn),
|
||||
compressionThreshold: -1,
|
||||
}
|
||||
for {
|
||||
packetID, packetData, err := player.readPacket()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
|
||||
switch player.state {
|
||||
case 0: // Handshake state
|
||||
switch packetID {
|
||||
case 0x00:
|
||||
version, address, port, nextState, err := getHandshakePacket(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
player.version = version
|
||||
player.requestedAddress = address
|
||||
player.requestedPort = port
|
||||
player.state = nextState
|
||||
if player.state == 3 {
|
||||
player.state = 2
|
||||
player.transferred = true
|
||||
}
|
||||
|
||||
world.Players = append(world.Players, player)
|
||||
default:
|
||||
errOut = fmt.Errorf("unexpected packet %x in handshake state", packetID)
|
||||
log.Println(errOut)
|
||||
}
|
||||
|
||||
case 1: // Status state
|
||||
switch packetID {
|
||||
case 0x00:
|
||||
err := player.sendStatusResponse("Hello there")
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
case 0x01:
|
||||
payload, err := getPingPacket(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendPongPacket(payload)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
default:
|
||||
errOut = fmt.Errorf("unexpected packet %x in status state", packetID)
|
||||
log.Println(errOut)
|
||||
}
|
||||
|
||||
case 2: // Login state
|
||||
switch packetID {
|
||||
case 0x00:
|
||||
username, playerUUID, err := getLoginStart(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
player.name = username
|
||||
player.uuid = playerUUID
|
||||
if !usernameRegex.MatchString(player.name) {
|
||||
errOut = errors.New("bad username")
|
||||
break
|
||||
}
|
||||
err = player.sendEncryptionRequest()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
case 0x01:
|
||||
sharedSecretFromClient, verifyTokenFromClient, err := getEncryptionResponse(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
decryptedVerifyTokenFromClient, err := rsa.DecryptPKCS1v15(rand.Reader, serverPrivateKey, verifyTokenFromClient)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
if !reflect.DeepEqual(player.verifyToken, decryptedVerifyTokenFromClient) {
|
||||
errOut = errors.New("wrong session verify token")
|
||||
break
|
||||
}
|
||||
decryptedSecret, err := rsa.DecryptPKCS1v15(rand.Reader, serverPrivateKey, sharedSecretFromClient)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
success, err := player.hasJoinedSession(decryptedSecret)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
if !success {
|
||||
errOut = errors.New("joined session not found")
|
||||
break
|
||||
}
|
||||
if player.profile.ID != strings.Replace(player.uuid.String(), "-", "", -1) || player.profile.Name != player.name {
|
||||
errOut = errors.New("wrong session name")
|
||||
break
|
||||
}
|
||||
player.encryptStream, player.decryptStream, err = NewCFB8Cipher(decryptedSecret)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendCompression()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
player.compressionThreshold = CompressionThreshold
|
||||
log.Println("sent compression")
|
||||
err = player.sendLoginSuccess()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
log.Println("sent login success")
|
||||
err = player.sendDataPacks()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendRegistryData()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendFeatureFlags()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendUpdateTags()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendReportDetails()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
err = player.sendServerLinks()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
|
||||
case 0x03:
|
||||
player.state = 3
|
||||
|
||||
default:
|
||||
errOut = fmt.Errorf("unexpected packet %x in login state", packetID)
|
||||
log.Println(errOut)
|
||||
}
|
||||
|
||||
case 3: //configuration
|
||||
switch packetID {
|
||||
case 0x00:
|
||||
locale, viewDistance, chatMode, chatColors, skinParts, isRightHanded, textFiltering, serverListing, err := getClientInformation(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
player.locale = locale
|
||||
player.viewDistance = viewDistance
|
||||
player.chatMode = chatMode
|
||||
player.chatColors = chatColors
|
||||
player.skinParts = skinParts
|
||||
player.isRightHanded = isRightHanded
|
||||
player.textFiltering = textFiltering
|
||||
player.serverListing = serverListing
|
||||
case 0x01:
|
||||
//TODO Implement cookie
|
||||
case 0x02:
|
||||
_, _, _ = getPluginPacket(packetData)
|
||||
case 0x03:
|
||||
player.state = 4
|
||||
go player.sendKeepAlives()
|
||||
err := player.sendPlayStart()
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
player.sendDifficulty()
|
||||
//player.send
|
||||
case 0x04:
|
||||
player.keepAliveReceivedTimestamp = time.Now().Unix()
|
||||
case 0x05:
|
||||
err := player.getPongPacket(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
case 0x06:
|
||||
err := player.getResourcePackPacket(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
case 0x07:
|
||||
err := player.getDataPacksPacket(packetData)
|
||||
if err != nil {
|
||||
errOut = err
|
||||
break
|
||||
}
|
||||
default:
|
||||
errOut = fmt.Errorf("unexpected packet %x in configration state", packetID)
|
||||
log.Println(errOut)
|
||||
}
|
||||
|
||||
case 4:
|
||||
switch packetID {
|
||||
|
||||
default:
|
||||
errOut = fmt.Errorf("unexpected packet %x in play state", packetID)
|
||||
log.Println(errOut)
|
||||
}
|
||||
|
||||
default:
|
||||
errOut = fmt.Errorf("unknown player state %d", player.state)
|
||||
break
|
||||
}
|
||||
if errOut != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if errOut != nil {
|
||||
log.Println(errOut.Error())
|
||||
_ = player.sendDisconnect(fmt.Sprintf("Your server thread crashed: %s", errOut.Error()))
|
||||
}
|
||||
|
||||
// Close connection
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
for index, client := range world.Players {
|
||||
if player.conn == client.conn {
|
||||
world.Players = append(world.Players[:index], world.Players[index+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user