This commit is contained in:
2024-09-18 19:58:25 +02:00
commit efd6d64d01
9 changed files with 2782 additions and 0 deletions

387
main.go Normal file
View 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
}
}
}