Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/GeyserMC/Geyser into serv…
Browse files Browse the repository at this point in the history
…er-inventory
  • Loading branch information
Camotoy committed Feb 16, 2021
2 parents ec23e40 + 1f1d94a commit aa47e75
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 97 deletions.
16 changes: 5 additions & 11 deletions connector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.8</version>
<version>2.10.2</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -175,25 +169,25 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.3.0</version>
<version>4.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.3.0</version>
<version>4.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>4.3.0</version>
<version>4.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
<version>4.3.0</version>
<version>4.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public class GeyserConnector {
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);

public static final String NAME = "Geyser";
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
import java.nio.charset.StandardCharsets;

public class ConnectorServerEventHandler implements BedrockServerEventHandler {
/*
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
*/
private static final int MINECRAFT_VERSION_BYTES_LENGTH = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length;
private static final int BRAND_BYTES_LENGTH = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8).length;
/**
* The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length.
*/
private static final int MAGIC_RAKNET_LENGTH = 338;

private final GeyserConnector connector;

Expand Down Expand Up @@ -69,16 +78,16 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {

BedrockPong pong = new BedrockPong();
pong.setEdition("MCPE");
pong.setGameType("Default");
pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59
pong.setNintendoLimited(false);
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
pong.setVersion(null); // Server tries to connect either way and it looks better
pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
pong.setIpv4Port(config.getBedrock().getPort());

if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
String mainMotd = motd[0]; // First line of the motd.
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default.

pong.setMotd(mainMotd.trim());
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
Expand All @@ -95,15 +104,28 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
pong.setMaximumPlayerCount(config.getMaxPlayers());
}

// Fallbacks to prevent errors and allow Bedrock to see the server
if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) {
pong.setMotd(GeyserConnector.NAME);
}
if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) {
// Sub-MOTD cannot be empty as of 1.16.210.59
pong.setSubMotd(GeyserConnector.NAME);
}

// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
// Remove the sub-MOTD first since that only appears locally
pong.setSubMotd("");
if (motdArray.length > 338) {
int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
// Shorten the sub-MOTD first since that only appears locally
if (subMotdLength > BRAND_BYTES_LENGTH) {
pong.setSubMotd(GeyserConnector.NAME);
subMotdLength = BRAND_BYTES_LENGTH;
}
if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
// If the top MOTD is still too long, we chop it down
byte[] newMotdArray = new byte[339];
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.BedrockClientData;
Expand Down Expand Up @@ -163,7 +164,7 @@ public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSessio
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());

// Store the skin and geometry for the ears
SkinProvider.storeEarSkin(entity.getUuid(), skin);
SkinProvider.storeEarSkin(skin);
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
}
}
Expand Down Expand Up @@ -267,7 +268,10 @@ public static GameProfileData from(GameProfile profile) {

return new GameProfileData(skinUrl, capeUrl, isAlex);
} catch (Exception exception) {
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage());
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
exception.printStackTrace();
}
return loadBedrockOrOfflineSkin(profile);
}
}
Expand All @@ -282,7 +286,7 @@ private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) {

String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) {
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());

if (session != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,12 @@ public class SkinProvider {
.build();
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();

public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false);
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();

public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
public static String EARS_GEOMETRY;
public static String EARS_GEOMETRY_SLIM;
public static SkinGeometry SKULL_GEOMETRY;
public static final String EARS_GEOMETRY;
public static final String EARS_GEOMETRY_SLIM;
public static final SkinGeometry SKULL_GEOMETRY;

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

Expand Down Expand Up @@ -229,15 +228,15 @@ public static CompletableFuture<Cape> requestUnofficialCape(Cape officialCape, U
return CompletableFuture.completedFuture(officialCape);
}

public static CompletableFuture<Skin> requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) {
public static CompletableFuture<Skin> requestEars(String earsUrl, boolean newThread, Skin skin) {
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);

CompletableFuture<Skin> future;
if (newThread) {
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE)
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE)
.whenCompleteAsync((outSkin, throwable) -> { });
} else {
Skin ears = supplyEars(skin, earsUrl, provider); // blocking
Skin ears = supplyEars(skin, earsUrl); // blocking
future = CompletableFuture.completedFuture(ears);
}
return future;
Expand All @@ -255,7 +254,7 @@ public static CompletableFuture<Skin> requestEars(String earsUrl, EarsProvider p
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
for (EarsProvider provider : EarsProvider.VALUES) {
Skin skin1 = getOrDefault(
requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin),
requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin),
officialSkin, 4
);
if (skin1.isEars()) {
Expand Down Expand Up @@ -295,12 +294,11 @@ public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte
}

/**
* Stores the ajusted skin with the ear texture to the cache
* Stores the adjusted skin with the ear texture to the cache
*
* @param playerID The UUID to cache it against
* @param skin The skin to cache
*/
public static void storeEarSkin(UUID playerID, Skin skin) {
public static void storeEarSkin(Skin skin) {
cachedSkins.put(skin.getTextureUrl(), skin);
}

Expand All @@ -324,7 +322,7 @@ private static Skin supplySkin(UUID uuid, String textureUrl) {
}

private static Cape supplyCape(String capeUrl, CapeProvider provider) {
byte[] cape = new byte[0];
byte[] cape = EMPTY_CAPE.getCapeData();
try {
cape = requestImage(capeUrl, provider);
} catch (Exception ignored) {} // just ignore I guess
Expand All @@ -334,7 +332,7 @@ private static Cape supplyCape(String capeUrl, CapeProvider provider) {
return new Cape(
capeUrl,
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
cape,
System.currentTimeMillis(),
cape.length == 0
);
Expand All @@ -345,10 +343,9 @@ private static Cape supplyCape(String capeUrl, CapeProvider provider) {
*
* @param existingSkin The players current skin
* @param earsUrl The URL to get the ears texture from
* @param provider The ears texture provider
* @return The updated skin with ears
*/
private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) {
private static Skin supplyEars(Skin existingSkin, String earsUrl) {
try {
// Get the ears texture
BufferedImage ears = ImageIO.read(new URL(earsUrl));
Expand Down Expand Up @@ -415,14 +412,15 @@ private static byte[] requestImage(String imageUrl, CapeProvider provider) throw

// if the requested image is a cape
if (provider != null) {
while(image.getWidth() > 64) {
image = scale(image);
if (image.getWidth() > 64) {
image = scale(image, 64, 32);
}
} else {
// Very rarely, skins can be larger than Minecraft's default.
// Bedrock will not render anything above a width of 128.
if (image.getWidth() > 128) {
image = scale(image, 128, image.getHeight() / (image.getWidth() / 128));
}
BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
Graphics g = newImage.createGraphics();
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
g.dispose();
image = newImage;
}

byte[] data = bufferedImageToImageData(image);
Expand Down Expand Up @@ -506,12 +504,13 @@ private static BufferedImage readFiveZigCape(String url) throws IOException {
return null;
}

private static BufferedImage scale(BufferedImage bufferedImage) {
BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null);
g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
g2.dispose();
bufferedImage.flush();
return resized;
}

Expand Down Expand Up @@ -579,17 +578,17 @@ public static <T> T getOrDefault(CompletableFuture<T> future, T defaultValue, in
@AllArgsConstructor
@Getter
public static class SkinAndCape {
private Skin skin;
private Cape cape;
private final Skin skin;
private final Cape cape;
}

@AllArgsConstructor
@Getter
public static class Skin {
private UUID skinOwner;
private String textureUrl;
private byte[] skinData;
private long requestedOn;
private final String textureUrl;
private final byte[] skinData;
private final long requestedOn;
private boolean updated;
private boolean ears;

Expand All @@ -603,19 +602,19 @@ private Skin(long requestedOn, String textureUrl, byte[] skinData) {
@AllArgsConstructor
@Getter
public static class Cape {
private String textureUrl;
private String capeId;
private byte[] capeData;
private long requestedOn;
private boolean failed;
private final String textureUrl;
private final String capeId;
private final byte[] capeData;
private final long requestedOn;
private final boolean failed;
}

@AllArgsConstructor
@Getter
public static class SkinGeometry {
private String geometryName;
private String geometryData;
private boolean failed;
private final String geometryName;
private final String geometryData;
private final boolean failed;

/**
* Generate generic geometry
Expand Down
Loading

0 comments on commit aa47e75

Please sign in to comment.