mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge remote-tracking branch 'origin/feature/floodgate-data-version' into feature/1.18
This commit is contained in:
commit
7df013daf9
15 changed files with 46 additions and 296 deletions
|
@ -70,8 +70,8 @@ public final class AesCipher implements FloodgateCipher {
|
||||||
cipherText = topping.encode(cipherText);
|
cipherText = topping.encode(cipherText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1)
|
return ByteBuffer.allocate(HEADER.length + iv.length + cipherText.length + 1)
|
||||||
.put(IDENTIFIER) // header
|
.put(HEADER)
|
||||||
.put(iv)
|
.put(iv)
|
||||||
.put((byte) 0x21)
|
.put((byte) 0x21)
|
||||||
.put(cipherText)
|
.put(cipherText)
|
||||||
|
@ -83,15 +83,15 @@ public final class AesCipher implements FloodgateCipher {
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
||||||
|
|
||||||
int bufferLength = cipherTextWithIv.length - HEADER_LENGTH;
|
int bufferLength = cipherTextWithIv.length - HEADER.length;
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength);
|
ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER.length, bufferLength);
|
||||||
|
|
||||||
int ivLength = IV_LENGTH;
|
int ivLength = IV_LENGTH;
|
||||||
|
|
||||||
if (topping != null) {
|
if (topping != null) {
|
||||||
int mark = buffer.position();
|
int mark = buffer.position();
|
||||||
|
|
||||||
// we need the first index, the second is for the optional RawSkin
|
// we need the first index, the second is for the actual data
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
while (buffer.hasRemaining() && !found) {
|
while (buffer.hasRemaining() && !found) {
|
||||||
if (buffer.get() == 0x21) {
|
if (buffer.get() == 0x21) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ public final class AesKeyProducer implements KeyProducer {
|
||||||
public SecretKey produce() {
|
public SecretKey produce() {
|
||||||
try {
|
try {
|
||||||
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
||||||
keyGenerator.init(KEY_SIZE, getSecureRandom());
|
keyGenerator.init(KEY_SIZE, secureRandom());
|
||||||
return keyGenerator.generateKey();
|
return keyGenerator.generateKey();
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new RuntimeException(exception);
|
throw new RuntimeException(exception);
|
||||||
|
@ -55,7 +55,7 @@ public final class AesKeyProducer implements KeyProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecureRandom getSecureRandom() throws NoSuchAlgorithmException {
|
private SecureRandom secureRandom() throws NoSuchAlgorithmException {
|
||||||
// use Windows-PRNG for windows (default impl is SHA1PRNG)
|
// use Windows-PRNG for windows (default impl is SHA1PRNG)
|
||||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||||
return SecureRandom.getInstance("Windows-PRNG");
|
return SecureRandom.getInstance("Windows-PRNG");
|
||||||
|
|
|
@ -26,33 +26,32 @@
|
||||||
|
|
||||||
package org.geysermc.floodgate.crypto;
|
package org.geysermc.floodgate.crypto;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.geysermc.floodgate.util.InvalidFormatException;
|
import org.geysermc.floodgate.util.InvalidFormatException;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for both encrypting and decrypting data
|
* Responsible for both encrypting and decrypting data
|
||||||
*/
|
*/
|
||||||
public interface FloodgateCipher {
|
public interface FloodgateCipher {
|
||||||
// use invalid username characters at the beginning and the end of the identifier,
|
int VERSION = 0;
|
||||||
// to make sure that it doesn't get messed up with usernames
|
byte[] IDENTIFIER = "^Floodgate^".getBytes(UTF_8);
|
||||||
byte[] IDENTIFIER = "^Floodgate^".getBytes(StandardCharsets.UTF_8);
|
byte[] HEADER = (new String(IDENTIFIER, UTF_8) + (char) (VERSION + 0x3E)).getBytes(UTF_8);
|
||||||
int HEADER_LENGTH = IDENTIFIER.length;
|
|
||||||
|
|
||||||
static boolean hasHeader(String data) {
|
static int version(String data) {
|
||||||
if (data.length() < IDENTIFIER.length) {
|
if (data.length() <= HEADER.length) {
|
||||||
return false;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < IDENTIFIER.length; i++) {
|
for (int i = 0; i < IDENTIFIER.length; i++) {
|
||||||
if (IDENTIFIER[i] != data.charAt(i)) {
|
if (IDENTIFIER[i] != data.charAt(i)) {
|
||||||
return false;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return data.charAt(IDENTIFIER.length) - 0x3E;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +78,7 @@ public interface FloodgateCipher {
|
||||||
* @throws Exception when the encryption failed
|
* @throws Exception when the encryption failed
|
||||||
*/
|
*/
|
||||||
default byte[] encryptFromString(String data) throws Exception {
|
default byte[] encryptFromString(String data) throws Exception {
|
||||||
return encrypt(data.getBytes(StandardCharsets.UTF_8));
|
return encrypt(data.getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,7 +103,7 @@ public interface FloodgateCipher {
|
||||||
if (decrypted == null) {
|
if (decrypted == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new String(decrypted, StandardCharsets.UTF_8);
|
return new String(decrypted, UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,7 +115,7 @@ public interface FloodgateCipher {
|
||||||
* @throws Exception when the decrypting failed
|
* @throws Exception when the decrypting failed
|
||||||
*/
|
*/
|
||||||
default byte[] decryptFromString(String data) throws Exception {
|
default byte[] decryptFromString(String data) throws Exception {
|
||||||
return decrypt(data.getBytes(StandardCharsets.UTF_8));
|
return decrypt(data.getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,37 +126,21 @@ public interface FloodgateCipher {
|
||||||
* @throws InvalidFormatException when the header is invalid
|
* @throws InvalidFormatException when the header is invalid
|
||||||
*/
|
*/
|
||||||
default void checkHeader(byte[] data) throws InvalidFormatException {
|
default void checkHeader(byte[] data) throws InvalidFormatException {
|
||||||
final int identifierLength = IDENTIFIER.length;
|
if (data.length <= HEADER.length) {
|
||||||
|
throw new InvalidFormatException(
|
||||||
if (data.length <= HEADER_LENGTH) {
|
"Data length is smaller then header." +
|
||||||
throw new InvalidFormatException("Data length is smaller then header." +
|
"Needed " + HEADER.length + ", got " + data.length
|
||||||
"Needed " + HEADER_LENGTH + ", got " + data.length,
|
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < identifierLength; i++) {
|
for (int i = 0; i < IDENTIFIER.length; i++) {
|
||||||
if (IDENTIFIER[i] != data[i]) {
|
if (IDENTIFIER[i] != data[i]) {
|
||||||
StringBuilder receivedIdentifier = new StringBuilder();
|
String identifier = new String(IDENTIFIER, UTF_8);
|
||||||
for (byte b : IDENTIFIER) {
|
String received = new String(data, 0, IDENTIFIER.length, UTF_8);
|
||||||
receivedIdentifier.append(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidFormatException(
|
throw new InvalidFormatException(
|
||||||
String.format("Expected identifier %s, got %s",
|
"Expected identifier " + identifier + ", got " + received
|
||||||
new String(IDENTIFIER, StandardCharsets.UTF_8),
|
|
||||||
receivedIdentifier.toString()
|
|
||||||
),
|
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
class HeaderResult {
|
|
||||||
private int version;
|
|
||||||
private int startIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.floodgate.time;
|
|
||||||
|
|
||||||
import java.net.DatagramPacket;
|
|
||||||
import java.net.DatagramSocket;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Thanks:
|
|
||||||
* https://datatracker.ietf.org/doc/html/rfc1769
|
|
||||||
* https://github.com/jonsagara/SimpleNtpClient
|
|
||||||
* https://stackoverflow.com/a/29138806
|
|
||||||
*/
|
|
||||||
public final class SntpClientUtils {
|
|
||||||
private static final int NTP_PORT = 123;
|
|
||||||
|
|
||||||
private static final int NTP_PACKET_SIZE = 48;
|
|
||||||
private static final int NTP_MODE = 3; // client
|
|
||||||
private static final int NTP_VERSION = 3;
|
|
||||||
private static final int RECEIVE_TIME_POSITION = 32;
|
|
||||||
|
|
||||||
private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L;
|
|
||||||
|
|
||||||
public static long requestTimeOffset(String host, int timeout) {
|
|
||||||
try (DatagramSocket socket = new DatagramSocket()) {
|
|
||||||
socket.setSoTimeout(timeout);
|
|
||||||
|
|
||||||
InetAddress address = InetAddress.getByName(host);
|
|
||||||
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE);
|
|
||||||
|
|
||||||
DatagramPacket request = new DatagramPacket(
|
|
||||||
buff.array(), NTP_PACKET_SIZE, address, NTP_PORT
|
|
||||||
);
|
|
||||||
|
|
||||||
// mode is in the least signification 3 bits
|
|
||||||
// version is in bits 3-5
|
|
||||||
buff.put((byte) (NTP_MODE | (NTP_VERSION << 3)));
|
|
||||||
|
|
||||||
long originateTime = System.currentTimeMillis();
|
|
||||||
socket.send(request);
|
|
||||||
|
|
||||||
DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE);
|
|
||||||
socket.receive(response);
|
|
||||||
|
|
||||||
long responseTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
// everything before isn't important for us
|
|
||||||
buff.position(RECEIVE_TIME_POSITION);
|
|
||||||
|
|
||||||
long receiveTime = readTimestamp(buff);
|
|
||||||
long transmitTime = readTimestamp(buff);
|
|
||||||
|
|
||||||
return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
return Long.MIN_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long readTimestamp(ByteBuffer buffer) {
|
|
||||||
//todo look into the ntp 2036 problem
|
|
||||||
long seconds = buffer.getInt() & 0xffffffffL;
|
|
||||||
long fraction = buffer.getInt() & 0xffffffffL;
|
|
||||||
return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.floodgate.time;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public final class TimeSyncer {
|
|
||||||
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
|
||||||
private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset
|
|
||||||
|
|
||||||
public TimeSyncer(String timeServer) {
|
|
||||||
executorService.scheduleWithFixedDelay(() -> {
|
|
||||||
// 5 tries to get the time offset, since UDP doesn't guaranty a response
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000);
|
|
||||||
if (offset != Long.MIN_VALUE) {
|
|
||||||
timeOffset = offset;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, 30, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
executorService.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimeOffset() {
|
|
||||||
return timeOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRealMillis() {
|
|
||||||
if (hasUsefulOffset()) {
|
|
||||||
return System.currentTimeMillis() + getTimeOffset();
|
|
||||||
}
|
|
||||||
return System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasUsefulOffset() {
|
|
||||||
return timeOffset != Long.MIN_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* @author GeyserMC
|
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.geysermc.floodgate.util;
|
|
||||||
|
|
||||||
public final class Base64Utils {
|
|
||||||
public static int getEncodedLength(int length) {
|
|
||||||
if (length <= 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 4 * ((length + 2) / 3);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,7 +28,6 @@ package org.geysermc.floodgate.util;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.geysermc.floodgate.time.TimeSyncer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This
|
* This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This
|
||||||
|
@ -38,7 +37,7 @@ import org.geysermc.floodgate.time.TimeSyncer;
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public final class BedrockData implements Cloneable {
|
public final class BedrockData implements Cloneable {
|
||||||
public static final int EXPECTED_LENGTH = 13;
|
public static final int EXPECTED_LENGTH = 12;
|
||||||
|
|
||||||
private final String version;
|
private final String version;
|
||||||
private final String username;
|
private final String username;
|
||||||
|
@ -54,25 +53,23 @@ public final class BedrockData implements Cloneable {
|
||||||
private final int subscribeId;
|
private final int subscribeId;
|
||||||
private final String verifyCode;
|
private final String verifyCode;
|
||||||
|
|
||||||
private final long timestamp;
|
|
||||||
private final int dataLength;
|
private final int dataLength;
|
||||||
|
|
||||||
public static BedrockData of(
|
public static BedrockData of(
|
||||||
String version, String username, String xuid, int deviceOs,
|
String version, String username, String xuid, int deviceOs,
|
||||||
String languageCode, int uiProfile, int inputMode, String ip,
|
String languageCode, int uiProfile, int inputMode, String ip,
|
||||||
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
|
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
|
||||||
String verifyCode, TimeSyncer timeSyncer) {
|
String verifyCode) {
|
||||||
return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode,
|
return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode,
|
||||||
uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode,
|
uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH);
|
||||||
timeSyncer.getRealMillis(), EXPECTED_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BedrockData of(
|
public static BedrockData of(
|
||||||
String version, String username, String xuid, int deviceOs,
|
String version, String username, String xuid, int deviceOs,
|
||||||
String languageCode, int uiProfile, int inputMode, String ip,
|
String languageCode, int uiProfile, int inputMode, String ip,
|
||||||
int subscribeId, String verifyCode, TimeSyncer timeSyncer) {
|
int subscribeId, String verifyCode) {
|
||||||
return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null,
|
return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null,
|
||||||
false, subscribeId, verifyCode, timeSyncer);
|
false, subscribeId, verifyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BedrockData fromString(String data) {
|
public static BedrockData fromString(String data) {
|
||||||
|
@ -86,12 +83,12 @@ public final class BedrockData implements Cloneable {
|
||||||
return new BedrockData(
|
return new BedrockData(
|
||||||
split[0], split[1], split[2], Integer.parseInt(split[3]), split[4],
|
split[0], split[1], split[2], Integer.parseInt(split[3]), split[4],
|
||||||
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer,
|
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer,
|
||||||
"1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length
|
"1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BedrockData emptyData(int dataLength) {
|
private static BedrockData emptyData(int dataLength) {
|
||||||
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1,
|
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null,
|
||||||
dataLength);
|
dataLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +102,7 @@ public final class BedrockData implements Cloneable {
|
||||||
return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' +
|
return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' +
|
||||||
languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' +
|
languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' +
|
||||||
(linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' +
|
(linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' +
|
||||||
(fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode + '\0' + timestamp;
|
(fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -59,7 +59,7 @@ public enum DeviceOs {
|
||||||
* @param id the DeviceOs identifier
|
* @param id the DeviceOs identifier
|
||||||
* @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found
|
* @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found
|
||||||
*/
|
*/
|
||||||
public static DeviceOs getById(int id) {
|
public static DeviceOs fromId(int id) {
|
||||||
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public enum InputMode {
|
||||||
* @param id the InputMode identifier
|
* @param id the InputMode identifier
|
||||||
* @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found
|
* @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found
|
||||||
*/
|
*/
|
||||||
public static InputMode getById(int id) {
|
public static InputMode fromId(int id) {
|
||||||
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,26 +26,8 @@
|
||||||
|
|
||||||
package org.geysermc.floodgate.util;
|
package org.geysermc.floodgate.util;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class InvalidFormatException extends Exception {
|
public class InvalidFormatException extends Exception {
|
||||||
private boolean header = false;
|
|
||||||
|
|
||||||
public InvalidFormatException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidFormatException(String message) {
|
public InvalidFormatException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvalidFormatException(String message, boolean header) {
|
|
||||||
super(message);
|
|
||||||
this.header = header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidFormatException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ public enum UiProfile {
|
||||||
* @param id the UiProfile identifier
|
* @param id the UiProfile identifier
|
||||||
* @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found
|
* @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found
|
||||||
*/
|
*/
|
||||||
public static UiProfile getById(int id) {
|
public static UiProfile fromId(int id) {
|
||||||
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
return VALUES.length > id ? VALUES[id] : VALUES[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,11 +81,11 @@ public enum WebsocketEventType {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebsocketEventType getById(int id) {
|
public static WebsocketEventType fromId(int id) {
|
||||||
return VALUES.length > id ? VALUES[id] : null;
|
return VALUES.length > id ? VALUES[id] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int id() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
import org.geysermc.floodgate.news.NewsItemAction;
|
import org.geysermc.floodgate.news.NewsItemAction;
|
||||||
import org.geysermc.floodgate.time.TimeSyncer;
|
|
||||||
import org.geysermc.geyser.api.GeyserApi;
|
import org.geysermc.geyser.api.GeyserApi;
|
||||||
import org.geysermc.geyser.command.CommandManager;
|
import org.geysermc.geyser.command.CommandManager;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
|
@ -110,7 +109,6 @@ public class GeyserImpl implements GeyserApi {
|
||||||
@Setter
|
@Setter
|
||||||
private static boolean shouldStartListener = true;
|
private static boolean shouldStartListener = true;
|
||||||
|
|
||||||
private final TimeSyncer timeSyncer;
|
|
||||||
private FloodgateCipher cipher;
|
private FloodgateCipher cipher;
|
||||||
private FloodgateSkinUploader skinUploader;
|
private FloodgateSkinUploader skinUploader;
|
||||||
private final NewsHandler newsHandler;
|
private final NewsHandler newsHandler;
|
||||||
|
@ -201,9 +199,7 @@ public class GeyserImpl implements GeyserApi {
|
||||||
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
|
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
|
||||||
TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false;
|
TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false;
|
||||||
|
|
||||||
TimeSyncer timeSyncer = null;
|
|
||||||
if (config.getRemote().getAuthType() == AuthType.FLOODGATE) {
|
if (config.getRemote().getAuthType() == AuthType.FLOODGATE) {
|
||||||
timeSyncer = new TimeSyncer(Constants.NTP_SERVER);
|
|
||||||
try {
|
try {
|
||||||
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
|
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
|
||||||
cipher = new AesCipher(new Base64Topping());
|
cipher = new AesCipher(new Base64Topping());
|
||||||
|
@ -214,7 +210,6 @@ public class GeyserImpl implements GeyserApi {
|
||||||
logger.severe(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception);
|
logger.severe(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.timeSyncer = timeSyncer;
|
|
||||||
|
|
||||||
String branch = "unknown";
|
String branch = "unknown";
|
||||||
int buildNumber = -1;
|
int buildNumber = -1;
|
||||||
|
@ -441,9 +436,6 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
scheduledThread.shutdown();
|
scheduledThread.shutdown();
|
||||||
bedrockServer.close();
|
bedrockServer.close();
|
||||||
if (timeSyncer != null) {
|
|
||||||
timeSyncer.shutdown();
|
|
||||||
}
|
|
||||||
if (skinUploader != null) {
|
if (skinUploader != null) {
|
||||||
skinUploader.close();
|
skinUploader.close();
|
||||||
}
|
}
|
||||||
|
@ -491,10 +483,6 @@ public class GeyserImpl implements GeyserApi {
|
||||||
return bootstrap.getWorldManager();
|
return bootstrap.getWorldManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSyncer getTimeSyncer() {
|
|
||||||
return timeSyncer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GeyserImpl getInstance() {
|
public static GeyserImpl getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -787,6 +787,8 @@ public class GeyserSession implements GeyserConnection, CommandSender {
|
||||||
FloodgateSkinUploader skinUploader = geyser.getSkinUploader();
|
FloodgateSkinUploader skinUploader = geyser.getSkinUploader();
|
||||||
FloodgateCipher cipher = geyser.getCipher();
|
FloodgateCipher cipher = geyser.getCipher();
|
||||||
|
|
||||||
|
System.out.println(new String(FloodgateCipher.HEADER, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
encryptedData = cipher.encryptFromString(BedrockData.of(
|
encryptedData = cipher.encryptFromString(BedrockData.of(
|
||||||
clientData.getGameVersion(),
|
clientData.getGameVersion(),
|
||||||
authData.name(),
|
authData.name(),
|
||||||
|
@ -797,17 +799,8 @@ public class GeyserSession implements GeyserConnection, CommandSender {
|
||||||
clientData.getCurrentInputMode().ordinal(),
|
clientData.getCurrentInputMode().ordinal(),
|
||||||
upstream.getAddress().getAddress().getHostAddress(),
|
upstream.getAddress().getAddress().getHostAddress(),
|
||||||
skinUploader.getId(),
|
skinUploader.getId(),
|
||||||
skinUploader.getVerifyCode(),
|
skinUploader.getVerifyCode()
|
||||||
geyser.getTimeSyncer()
|
|
||||||
).toString());
|
).toString());
|
||||||
|
|
||||||
if (!geyser.getTimeSyncer().hasUsefulOffset()) {
|
|
||||||
geyser.getLogger().warning(
|
|
||||||
"We couldn't make sure that your system clock is accurate. " +
|
|
||||||
"This can cause issues with logging in."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
||||||
|
|
|
@ -87,7 +87,7 @@ public final class FloodgateSkinUploader {
|
||||||
}
|
}
|
||||||
|
|
||||||
int typeId = node.get("event_id").asInt();
|
int typeId = node.get("event_id").asInt();
|
||||||
WebsocketEventType type = WebsocketEventType.getById(typeId);
|
WebsocketEventType type = WebsocketEventType.fromId(typeId);
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
logger.warning(String.format(
|
logger.warning(String.format(
|
||||||
"Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!",
|
"Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!",
|
||||||
|
|
Loading…
Reference in a new issue