Клиент говорит с сервером по нашему протоколу
Раньше клиент умел только показывать меню. Теперь он заходит на сервер,
получает наш мир и рисует его.
Как это устроено:
- Наш сетевой слой (net/rustnotmc/client/net) читает наш протокол и
переводит его пакеты в пакеты игры. Игра остаётся игрой: у неё есть
мир, игрок, сущности и рендер, и все они работают. Не переписывать их
ради того, чтобы нарисовать тот же мир иначе, незачем.
- Пакеты игры, которые не имеют соответствия у нас, не отправляются, а
наоборот: перемещение игрока и ломание блока переводятся в наши.
- Экран входа игры (GuiConnecting) заменён наш: он спрашивает у лаунчера,
кем пришёл игрок, и подставляет это в рукопожатие.
- Соединение качается из тика игры, а не из экрана. Рукопожатие, которое
зависит от того, что экран ещё на экране, ломается alt-tab'ом.
Вход теперь через лаунчер, а не внутри игры:
- Лаунчер уже умеет логинить игрока и отдаёт игре имя, uuid и токен
(--username, --uuid, --accessToken). Это та же роль, что у Yggdrasil в
ванилле, только лаунчер наш.
- Сервер спрашивает лаунчер, один из ли наш этот игрок, и заодно получает
бан-лист. Токен используется один раз и не хранится.
- Свои аккаунты сервера, когда настроен лаунчер, закрыты: сервер с двумя
дверями не имеет дверей. Открыть их можно флагом, но намеренно.
- Если игру запустили без лаунчера, экран всё равно спрашивает имя и
пароль: это путь для своего сервера и для разработки.
Проверено живьём: клиент вошёл по токену лаунчера, сервер спросил
лаунчер, лаунчер сказал "свой", игрок получил мир, 210 чанков пришли и
лежат на диске, рельеф нарисован.
Что чинилось по дороге:
- Minecraft.setIngameFocus() возвращал на главное меню, когда окно
получало фокус. Игрок как раз кликает в окно, чтобы напечатать пароль, и
экран входа исчезал в этот самый момент.
- Ответ лаунчера разбирается, а не ищется подстрокой: {"valid": true} и
{"valid":true} это один и тот же ответ, и сервер, который понимает
только второй, ломает работающий лаунчер.
This commit is contained in:
@@ -92,6 +92,16 @@ public final class Packets {
|
||||
public static final int ENTITY_SPAWN = 0x18;
|
||||
public static final int ENTITY_REMOVE = 0x19;
|
||||
|
||||
/**
|
||||
* The launcher's session, presented to the game server.
|
||||
*
|
||||
* The launcher logs the player in once and hands the game a token. The
|
||||
* game server asks the launcher whether that token is good, and the player
|
||||
* is in. This is the part of the handshake that Yggdrasil does in
|
||||
* vanilla, done with our own launcher instead of Mojang's.
|
||||
*/
|
||||
public static final int LAUNCHER_AUTH = 0x1A;
|
||||
|
||||
public static final int DISCONNECT = 0x7F;
|
||||
|
||||
/** The client introducing itself, and naming the account it wants. */
|
||||
@@ -740,6 +750,38 @@ public final class Packets {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The launcher's session, as the game server sees it.
|
||||
*
|
||||
* The token is a bearer credential: whoever holds it is the player. It is
|
||||
* sent to the game server and nowhere else, and the game server uses it
|
||||
* once, to ask the launcher whether it is good.
|
||||
*/
|
||||
public static final class LauncherAuth implements Packet {
|
||||
public final String account;
|
||||
public final String uuid;
|
||||
public final String token;
|
||||
|
||||
public LauncherAuth(String account, String uuid, String token) {
|
||||
this.account = account;
|
||||
this.uuid = uuid;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public Buf write() {
|
||||
Buf buf = new Buf(512);
|
||||
buf.writeString(this.account);
|
||||
buf.writeString(this.uuid);
|
||||
buf.writeString(this.token);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static LauncherAuth read(Buf in) {
|
||||
return new LauncherAuth(in.readString(MAX_TEXT), in.readString(MAX_TEXT),
|
||||
in.readString(MAX_TEXT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every packet id this build knows, and what it decodes into.
|
||||
*
|
||||
@@ -764,6 +806,7 @@ public final class Packets {
|
||||
map.put(Integer.valueOf(PONG), "Pong");
|
||||
map.put(Integer.valueOf(ENTITY_SPAWN), "EntitySpawn");
|
||||
map.put(Integer.valueOf(ENTITY_REMOVE), "EntityRemove");
|
||||
map.put(Integer.valueOf(LAUNCHER_AUTH), "LauncherAuth");
|
||||
map.put(Integer.valueOf(DISCONNECT), "Disconnect");
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -470,6 +470,14 @@ public final class ProtocolTest {
|
||||
Packets.EntityRemove remove = new Packets.EntityRemove(11);
|
||||
eq("remove", Integer.valueOf(11), Integer.valueOf(
|
||||
Packets.EntityRemove.read(new Buf(remove.write().toByteArray())).entityId));
|
||||
|
||||
Packets.LauncherAuth launcher = new Packets.LauncherAuth("Player",
|
||||
"00000000-0000-0000-0000-000000000000", "a.b.c");
|
||||
Packets.LauncherAuth launcherBack =
|
||||
Packets.LauncherAuth.read(new Buf(launcher.write().toByteArray()));
|
||||
eq("launcher account", "Player", launcherBack.account);
|
||||
eq("launcher uuid", "00000000-0000-0000-0000-000000000000", launcherBack.uuid);
|
||||
eq("launcher token", "a.b.c", launcherBack.token);
|
||||
}
|
||||
|
||||
private static void testChunkCompression() {
|
||||
@@ -519,7 +527,7 @@ public final class ProtocolTest {
|
||||
Packets.LOGIN, Packets.LOGIN_ACK, Packets.JOIN_WORLD,
|
||||
Packets.WORLD_JOIN, Packets.CHUNK_DATA, Packets.BLOCK_CHANGE,
|
||||
Packets.PLAYER_MOVE, Packets.ENTITY_MOVE, Packets.PING, Packets.PONG,
|
||||
Packets.ENTITY_SPAWN, Packets.ENTITY_REMOVE,
|
||||
Packets.ENTITY_SPAWN, Packets.ENTITY_REMOVE, Packets.LAUNCHER_AUTH,
|
||||
Packets.DISCONNECT,
|
||||
};
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
|
||||
Reference in New Issue
Block a user