Minecraft Bedrock сервер на Go. Часть #1 +1



Источник: https://github.com/Sandertv/gophertunnel
Источник: https://github.com/Sandertv/gophertunnel

Для реализации нашего сервера, мы будем использовать библиотеку Sandertv/gophertunnel. В этой части туториала, мы напишем свой тунель(прокси?), основываясь на примере из официального репозитория библиотеки, немного упростив его.

Предисловие

Bedrock версия использует надстройку над UDP - Raknet. Подробная документация и варианты реализации протокола на других языках, доступны здесь. Написание Minecraft сервера, подразумевает под собой написание прокси между оригинальным сервером и клиетами. Вы получаете возможность манипулировать данными, которыми обмениваются клиент и сервер. Статья подразумевает, что вы обладаете базовыми познаниями в Go и умеете разворачивать оригинальный сервер Minecraft.

Базовый прокси

package main

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"sync"
)

func main() {
	listener, _ := minecraft.ListenConfig{
		AuthenticationDisabled: true, // Отключаем авторизацию, чтобы не мучаться с входом в xbox, не забудьте отключить ее и в оригинальном сервере
	}.Listen("raknet", ":19130") // Стартуем наш прокси сервер

	for {
		c, _ := listener.Accept()

		go handleConnection(c.(*minecraft.Conn))
	}
}

func handleConnection(conn *minecraft.Conn) {
	dialer, _ := minecraft.Dialer{
		ClientData: conn.ClientData(),
	}.Dial("raknet", ":19131") // Клиент к оригинальному серверу

	g := sync.WaitGroup{}

	g.Add(2)

	/* Начинаем игру и спавним игрока */
	go func() {
		conn.StartGame(dialer.GameData())

		g.Done()
	}()

	go func() {
		dialer.DoSpawn()

		g.Done()
	}()

	g.Wait()

	// Передаем данные полученные от игрока на оригинальный сервер
	go func() {
		for {
			pk, _ := conn.ReadPacket()

			dialer.WritePacket(pk)
		}
	}()

	// Передаем данные полученые от оригинального сервера, подключенному клиенту
	go func() {
		for {
			pk, _ := dialer.ReadPacket()
			conn.WritePacket(pk)
		}
	}()
}

Теперь у нас есть прокси сервер, который пока что просто пропускает через себя запросы. Он не умеет закрывать соединение, не обрабатывает ошибки, падает если какой либо клиент отключается.

два клиента, один из которых подключен к оригинальному серверу
два клиента, один из которых подключен к оригинальному серверу

Немного модифицируем "слушающие" горутины, чтобы наш сервер научился закрывать соединение и не падать когда какой либо клиент отключается:

// Передаем данные полученные от игрока на оригинальный сервер
	go func() {
		defer listener.Disconnect(conn, "connection lost")
		defer dialer.Close()

		for {
			pk, err := conn.ReadPacket()
			if err != nil {
				return
			}

			err = dialer.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()

	// Передаем данные полученые от оригинального сервера, подключенному клиенту
	go func() {
		defer dialer.Close()
		defer listener.Disconnect(conn, "connection lost")

		for {
			pk, err := dialer.ReadPacket()
			if err != nil {
				return
			}

			err = conn.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()

Чтобы не дублировать в наш прокси сервер, статусные данные оригинального сервера, такие как название, кол-во игроков и прочее(подробее тут), настроим провайдер данных в виде оригинального сервера:

func main() {
	p, _ := minecraft.NewForeignStatusProvider(":19131") // В качестве провайдера данных, используем оригинальный сервер

	listener, _ := minecraft.ListenConfig{
		StatusProvider:         p,
		AuthenticationDisabled: true, // Отключаем авторизацию, чтобы не мучаться с входом в xbox, не забудьте отключить ее и в оригинальном сервере
	}.Listen("raknet", ":19130") // Стартуем наш прокси сервер

	for {
		c, _ := listener.Accept()

		go handleConnection(c.(*minecraft.Conn), listener)
	}
}

Это всё-таки прокси, а не сервер

В Minecraft Education Edition, с версии 1.0.1, появился "Agent". Этакий управляемый вашим кодом NPC. Принцип добавления функциональности для серверов Minecraft, чем то схож с принципом программирования "агентов". Все что вы можете делать, это пропускать данные от клиента к серверу и наоборот. Таким образом вы можете действовать от лица конкретных пользователей(не обязательно), фильтровать сообщения, запрещать или разрешать соединение с оригинальным сервером, логировать действия клиентов. Все ограничивается, только вашей фантазией.

Эмуляция клиента

Мы можем создать диалер, не привязываясь к реальному клиенту. Это может быть полезно для выполнения действий от именни "Сервера". По сути мы программируем своего "Агента", который не обязательно должен присуствовать в качестве физического объекта:

package main

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"github.com/sandertv/gophertunnel/minecraft/protocol/login"
	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
	"time"
)

func main() {
	dialer, _ := minecraft.Dialer{
		IdentityData: login.IdentityData{DisplayName: "Server"},
	}.Dial("raknet", ":19131") // Клиент к оригинальному серверу

	for {
		time.Sleep(10 * time.Second)

		dialer.WritePacket(&packet.Text{
			TextType:       packet.TextTypeChat,
			SourceName:     "MainDialer",
			Message:        "Сообщение от сервера",
			XUID:           "",
			PlatformChatID: "",
		})
	}
}

Такой подход, может первое время сбивать с толку и казаться очень урезанным, но на самом деле он открывает огромные возможности по модификации игрового процесса. В последующих частях, мы будем постепенно наращивать возможности и надежность нашего сервера.




Комментарии (9):

  1. ErgoZru
    /#23941803 / +3

    Так сервер или все таки прокси/туннель? Заголовок громкий, а по факту... Я уже начал думать что кто-то начал писать полноценный сервер на гошке, с описанием протокола, особенностей, того как что работает... А по факту.. ну такое...

    • DavidNadejdin
      /#23942263 / +1

      протокол полностью реализован в библиотеке, ведь она полноценно позволяет общаться как с клиентом, так и с сервером. Когда встает вопрос написание полноценного сервера, встает вопрос написания полноценного Minecraft, просто с вырезанной визуальной частью. На самом деле люди пилят https://github.com/NiclasOlofsson/MiNET. Но это проект ради проекта. Сейчас Minecraft сервера, пишутся именно через написание тунеля. И можно задаться вопросом - зачем по другому? Вероятнее всего, часть вашего полностью самописного сервера, будет выполнять действия похожие на те, что мы делаем когда пишем прокси. Да, у нас не будет диалера к основному серверу, но мы должны будем передавать действия клиента той части приложения, которая будет отвечать за запись той же ворлд даты и принимать данные которые она будет присылать обратно, рассылая их по клиентам. По итогу наш сервер будет выполнять все те-же действия, как и в случае с написанием тунеля. Так какой тогда смысл в реверс инженеринге оригинального сервера, если та часть которая связана с добавлением функционала и модификацией игрового процесса, будет выглядеть практически также. Надеюсь смог объяснить

      • ErgoZru
        /#23947083 / +1

        Нет, майнкрафт сервера не пишутся на туннелях. Это просто сравнение теплого и мягкого. И видимо ни про сервера на php для Minecraft PE, ни про Bukkit, ни про NukkitX, ни про еще штук 5-6 именно серверов, к которым пишутся уже плагины вы не слышали. Ок, если вы говорите про сервер, то каким образом вы можете повлиять на реальную логику обработки данных? Их хранения? К примеру у МК есть проблема с тем что нельзя горизонтально масштабировать сервер, потому что работа идет с файлами, ни о каких s3 и чего-то подобного, или какой либо альтернативы нет. Как вы своим сервером это решите? Я честно говоря слабо себе это представляю. Буду конечно же рад, если переубедите в дальнейшем и буду ждать новых постов по этой теме. Но как по мне название на данный момент слишком громкое и некоторые утверждения вызывают недоумение (про сервера которые делаются через туннели....). Но посмотрим что будет дальше, вторую часть прочитал, вроде написано про бан, но крайне скудно, то есть тупо сравнили ник и ответили клиенту дисконнектом.. очень плохо раскрыта эта тема, хотя можно было рассказать по каким полям сравнивать можно, на что и как реагировать, как среагировать на какое либо действие в игре и дисконнектнуть с занесением в бан лист который храится в кэше/базе/файле и тд....

        • DavidNadejdin
          /#23947407

          Я конечно слышал про Nukkit. И он способен безусловно работать без оригинального движка Minecraft. Вопрос скорее в том, будет ли он работать также? На скриншоте изображенные мобы, на сервере Nukkit, которые просто не знают что им делать и находятся в "подвисшем" состоянии:

          Наверняка есть плагины которые их оживляют. Тут стоит вопрос, пишем ли мы сервер minecraft или сервер который будет поддерживать клиент Minecraft? Как я и писал во 2 части, протокол полностью описан. Например вы можете написать полностью свой клиент Minecraft. Но с клиентом ситуация довольно простая, рисуйте на основе полученных данных и отправляйте соответсвующие протоколу данные. С сервером ситуация обстоит немного сложнее, так как движок оригинального Minecraft предсталвяет из себя как раз таки сервер с UDP интерфейсом. Вы конечно можете написать полностью свой сервер, со своей логикой всего, главное отдайте клиенту нужные данные. Но давайте снова спросим себя - мы делаем сервер minecraft или сервер который будет похож на minecraft? Да, какие-то задачи вы никак не решите написанием прокси. Но в 2 части речь шла скорее про то, что задачи которые вы сможете решить через написание прокси, будут решаться скорее всего точно также, если бы вы решали их через написание полностью своего сервера, но при этом перед вами бы стояла задача реализации логики ожидаеммой от Minecraft. Почему я считаю подход с использованием оригинального сервера Minecraft все таки написанием сервера, а не просто прокси/тунеля? Потому что никто не обязывает вас полностью передавать пакеты между оригинальным сервером и клиентом. Оригинальный сервер тут используется в качестве такого не микро микро-сервиса, которому вы делегируете задачу по получению поведения обычного minecraft, о чем и было сказанно во 2 части статьи. Такой подход можно в каком то смысле называть гибридным, если так вам будет меньше резать слух. Вы можете принять решение обработать какой то клиентский пакет или делегировать его оригинальному движку. Вы можете не передать пакет отправленный клиенту оригинальным сервером и/или отправить свой, главное чтобы он соотвествовал протоколу. Какой подход выбрать, дело случая. Но как мне кажется, когда вам например нужно ввести свою аутентификацию, нет никакого смысла пытаться реализовать весь движок Minecraft в своем проекте. Поэтому и был продемонстрирован пример конкретно с баном. Да, он довольно простой, но как мне показалось довольно демонстративный. Ведь зачем усложять код нагромождениями работы с бд, где у нас будет бан лист, когда и так очевидно что вместо строки может быть любое динамическое значение. Да, прочих деталей мало, но в 2 части была скорее заложена основа основ и подробнее описано то, почему мы все таки пишем сервер(используя оригинальный движок Minecraft). В последующих частях, если дойдут руки, вероятнее всего, я покажу работу конкретно с пакетами которые присылает нам сервер и клиент. Но Go все таки жестко типизированный язык и очень легко узнать самому, какими данными мы можем манимулировать. Но мне кажется, изза того, что способов реализации сервера несколько, мы так и будем из части в часть спорить, сервер это или не сервер. Аналогия с базами данных мне показалась хорошей, но видимо показалась

        • DavidNadejdin
          /#23947621

          Да, может быть мы можем улучшить сетевую часть сервера, написав его с нуля. Но мы вынужденные будем писать и сам движок с нуля. Интересно это? Конечно. Сделаем ли мы это лучше чем Mojang? Спорно. Все таки есть грань между написанием сервера и движка, как мне кажется довольно очевидна

        • DavidNadejdin
          /#23948663

          Аналогия с базами данных может казаться не корректной. Да, реализуя свой сервер базы данных, мы решаем какие-то определенные проблемы связанные с хранением данных. Пускай в теории наша самописная база данных будет изобретена только для хранения файлов в s3. Но например приложение, которое покрывает данные бизнес логикой, разве призванно хоть как то решать проблемы базы данных, даже если бы вся связка общалась по одному протоколу?

          Просто там где у нас например идет связка:

          MySQL<->Laravel<->Frontend

          У нас идёт связка:

          Minecraft Engine<->Our server<->Minecraft client

          Конечно логика обработки данных у нас не такая понятная, как в случае с синхронными http приложенями.

          Просто давайте помнить что те-же базы данных, имеют как ядро, так и сервер который решает как предоставлять апи для клиентов

    • Saiv46
      /#23942651

      Ну, полноценного сервера нету ни на одном языке программирования.

      Самый близкий к этому проект - PrismarineJS. На JacaScript и протокол описали, и бот с прокси поддерживают, а недавно и веб-клиент игры пилят, но сервер же до сих пор на 1.16.1 застрял и не поддерживает бОльшую часть механик игры.

      • DavidNadejdin
        /#23942675

        PrismarineJS насколько я знаю, орентирован на java версию игры. Gophertunnel орентирован на Bedrock, у которого свой протокол, основаный на старом протоколе от Minecraft PE. Библиотека полностью описывает актуальную версию протокола

      • DavidNadejdin
        /#23942691

        В планах развить прокси скрипт описаный в данной части статьи, в последующих частях, до полнофункционального сервера, с web интерфейсом, модулями, лоад балансерами. Но тема для меня новая, поэтому процесс написания новых частей может задержаться :)