3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-10-02 16:10:10 +02:00

Move custom skull config stuff to its own file

Custom skulls can now be added by username, uuid, and textures

Move skull nbt stuff from requestTexturesFromUsername to
SkullBlockEntityTranslator
Add requestTexturesFromUUID
Dieser Commit ist enthalten in:
davchoo 2022-07-10 21:08:07 -04:00
Ursprung a577512dac
Commit 49550a513c
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: A0168C8E45799B7D
8 geänderte Dateien mit 208 neuen und 57 gelöschten Zeilen

Datei anzeigen

@ -95,9 +95,6 @@ public interface GeyserConfiguration {
boolean isAddCustomSkullBlocks(); boolean isAddCustomSkullBlocks();
// TODO this should probably go in a different file?
List<String> getCustomSkullProfiles();
boolean isAboveBedrockNetherBuilding(); boolean isAboveBedrockNetherBuilding();
boolean isForceResourcePacks(); boolean isForceResourcePacks();

Datei anzeigen

@ -0,0 +1,58 @@
/*
* Copyright (c) 2019-2022 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.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
public class GeyserCustomSkullConfiguration {
@JsonProperty("player-usernames")
private List<String> playerUsernames;
@JsonProperty("player-uuids")
private List<String> playerUUIDs;
@JsonProperty("textures")
private List<String> textures;
public List<String> getPlayerUsernames() {
return Objects.requireNonNullElse(playerUsernames, Collections.emptyList());
}
public List<String> getPlayerUUIDs() {
return Objects.requireNonNullElse(playerUUIDs, Collections.emptyList());
}
public List<String> getTextures() {
return Objects.requireNonNullElse(textures, Collections.emptyList());
}
}

Datei anzeigen

@ -148,9 +148,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("add-custom-skull-blocks") @JsonProperty("add-custom-skull-blocks")
boolean addCustomSkullBlocks = false; boolean addCustomSkullBlocks = false;
@JsonProperty("custom-skull-profiles")
List<String> customSkullProfiles = Collections.emptyList();
@JsonProperty("force-resource-packs") @JsonProperty("force-resource-packs")
private boolean forceResourcePacks = true; private boolean forceResourcePacks = true;

Datei anzeigen

@ -27,45 +27,100 @@ package org.geysermc.geyser.registry.populator;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.configuration.GeyserCustomSkullConfiguration;
import org.geysermc.geyser.pack.SkullResourcePackManager; import org.geysermc.geyser.pack.SkullResourcePackManager;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.registry.type.CustomSkull;
import org.geysermc.geyser.skin.SkinManager; import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
public class CustomSkullRegistryPopulator { public class CustomSkullRegistryPopulator {
public static void populate() { public static void populate() {
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
if (!GeyserImpl.getInstance().getConfig().isAddCustomSkullBlocks()) {
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap()); BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
if (!GeyserImpl.getInstance().getConfig().isAddCustomSkullBlocks()) {
return; return;
} }
Map<String, CustomSkull> customSkulls = new Object2ObjectOpenHashMap<>(); GeyserCustomSkullConfiguration skullConfig;
for (String skullProfile : GeyserImpl.getInstance().getConfig().getCustomSkullProfiles()) {
try { try {
SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(skullProfile); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
Path skullConfigPath = bootstrap.getConfigFolder().resolve("custom-skulls.yml");
File skullConfigFile = FileUtils.fileOrCopiedFromResource(skullConfigPath.toFile(), "custom-skulls.yml", Function.identity(), bootstrap);
skullConfig = FileUtils.loadConfig(skullConfigFile, GeyserCustomSkullConfiguration.class);
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
return;
}
BlockRegistries.CUSTOM_SKULLS.set(new Object2ObjectOpenHashMap<>());
List<String> textures = new ArrayList<>(skullConfig.getTextures());
// TODO see if this can be cleaned up any better
for (String username : skullConfig.getPlayerUsernames()) {
try {
String texture = SkinProvider.requestTexturesFromUsername(username).get();
if (texture == null) {
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.");
continue;
}
textures.add(texture);
} catch (InterruptedException | ExecutionException e) {
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.", e);
}
}
for (String uuid : skullConfig.getPlayerUUIDs()) {
try {
String uuidDigits = uuid.replace("-", "");
if (uuidDigits.length() != 32) {
GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block.");
continue;
}
String texture = SkinProvider.requestTexturesFromUUID(uuid).get();
if (texture == null) {
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.");
continue;
}
textures.add(texture);
} catch (InterruptedException | ExecutionException e) {
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e);
}
}
for (String texture : textures) {
try {
SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(texture);
if (profileData == null) { if (profileData == null) {
GeyserImpl.getInstance().getLogger().warning("Skull profile " + skullProfile + " contained no skins and will not be added as a custom block."); GeyserImpl.getInstance().getLogger().warning("Skull texture " + texture + " contained no skins and will not be added as a custom block.");
continue; continue;
} }
try { try {
String skinUrl = profileData.skinUrl(); String skinUrl = profileData.skinUrl();
String skinHash = skinUrl.substring(skinUrl.lastIndexOf("/") + 1); String skinHash = skinUrl.substring(skinUrl.lastIndexOf("/") + 1);
SkullResourcePackManager.cacheSkullSkin(skinUrl, skinHash); SkullResourcePackManager.cacheSkullSkin(skinUrl, skinHash);
customSkulls.put(skinHash, new CustomSkull(skinHash)); BlockRegistries.CUSTOM_SKULLS.register(skinHash, new CustomSkull(skinHash));
} catch (IOException e) { } catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull profile " + skullProfile + " This skull will not be added as a custom block.", e); GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull texture " + texture + " This skull will not be added as a custom block.", e);
} }
} catch (IOException e) { } catch (IOException e) {
GeyserImpl.getInstance().getLogger().error("Skull profile " + skullProfile + " is invalid and will not be added as a custom block.", e); GeyserImpl.getInstance().getLogger().error("Skull texture " + texture + " is invalid and will not be added as a custom block.", e);
} }
} }
GeyserImpl.getInstance().getLogger().debug("Registered " + customSkulls.size() + " custom skulls as custom blocks.");
BlockRegistries.CUSTOM_SKULLS.set(customSkulls); GeyserImpl.getInstance().getLogger().debug("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks.");
} }
} }

Datei anzeigen

@ -544,48 +544,23 @@ public class SkinProvider {
} }
/** /**
* If a skull has a username but no textures, request them. * Request textures from a player's UUID
* *
* @param skullOwner the CompoundTag of the skull with no textures * @param uuid the player's UUID without any hyphens
* @return a completable GameProfile with textures included * @return a completable GameProfile with textures included
*/ */
public static CompletableFuture<String> requestTexturesFromUsername(CompoundTag skullOwner) { public static CompletableFuture<String> requestTexturesFromUUID(String uuid) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
Tag uuidTag = skullOwner.get("Id");
String uuidToString = "";
JsonNode node;
boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check
if (!retrieveUuidFromInternet) {
int[] uuidAsArray = ((IntArrayTag) uuidTag).getValue();
// thank u viaversion
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
retrieveUuidFromInternet = uuid.version() != 4;
uuidToString = uuid.toString().replace("-", "");
}
try { try {
if (retrieveUuidFromInternet) { JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid);
// Offline skin, or no present UUID
node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + skullOwner.get("Name").getValue());
JsonNode id = node.get("id");
if (id == null) {
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + skullOwner.get("Name").getValue());
return null;
}
uuidToString = id.asText();
}
// Get textures from UUID
node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString);
JsonNode properties = node.get("properties"); JsonNode properties = node.get("properties");
if (properties == null) { if (properties == null) {
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid);
return null; return null;
} }
return node.get("properties").get(0).get("value").asText(); return node.get("properties").get(0).get("value").asText();
} catch (Exception e) { } catch (Exception e) {
GeyserImpl.getInstance().getLogger().debug("Unable to request textures for " + uuid);
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace(); e.printStackTrace();
} }
@ -594,6 +569,37 @@ public class SkinProvider {
}, EXECUTOR_SERVICE); }, EXECUTOR_SERVICE);
} }
/**
* Request textures from a player's username
*
* @param username the player's username
* @return a completable GameProfile with textures included
*/
public static CompletableFuture<String> requestTexturesFromUsername(String username) {
return CompletableFuture.supplyAsync(() -> {
try {
// Offline skin, or no present UUID
JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username);
JsonNode id = node.get("id");
if (id == null) {
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username);
return null;
}
return id.asText();
} catch (Exception e) {
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace();
}
return null;
}
}, EXECUTOR_SERVICE).thenCompose(uuid -> {
if (uuid == null) {
return CompletableFuture.completedFuture(null);
}
return requestTexturesFromUUID(uuid);
});
}
private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException {
if (provider == CapeProvider.FIVEZIG) if (provider == CapeProvider.FIVEZIG)
return readFiveZigCape(imageUrl); return readFiveZigCape(imageUrl);

Datei anzeigen

@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
@ -39,6 +40,7 @@ import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.skin.SkinProvider;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -62,7 +64,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
if (owner != null) { if (owner != null) {
CompoundTag properties = owner.get("Properties"); CompoundTag properties = owner.get("Properties");
if (properties == null) { if (properties == null) {
return SkinProvider.requestTexturesFromUsername(owner); if (owner.get("Id") instanceof IntArrayTag uuidTag) {
int[] uuidAsArray = uuidTag.getValue();
// thank u viaversion
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
if (uuid.version() == 4) {
String uuidString = uuid.toString().replace("-", "");
return SkinProvider.requestTexturesFromUUID(uuidString);
}
}
if (owner.get("Name") instanceof StringTag nameTag) {
// Fall back to username if UUID was missing or was an offline mode UUID
return SkinProvider.requestTexturesFromUsername(nameTag.getValue());
}
return CompletableFuture.completedFuture(null);
} }
ListTag textures = properties.get("textures"); ListTag textures = properties.get("textures");

Datei anzeigen

@ -165,14 +165,11 @@ add-non-bedrock-items: true
# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching. # This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching.
add-custom-blocks: true add-custom-blocks: true
# Whether to allow some custom skulls to be translated as custom blocks. This requires `add-custom-blocks` and `allow-custom-skulls` to be # Whether to allow some custom skulls to be translated as custom blocks and displayed in the inventory and on entities.
# enabled. This will generate a resource pack for Bedrock players and enables `force-resource-packs` # This requires `add-custom-blocks` and `allow-custom-skulls` to be enabled.
add-custom-skull-blocks: true # This will generate a resource pack for Bedrock players and enables `force-resource-packs`.
# Custom skulls can be added in `custom-skulls.yml`
# List of custom skull profiles to translate as custom blocks. This requires `add-custom-skull-blocks` to be enabled. add-custom-skull-blocks: false
# This is the Value stored in the SkullOwner of the custom skull
custom-skull-profiles:
- ewogICJ0aW1lc3RhbXAiIDogMTY1NzMyMjIzOTgzMywKICAicHJvZmlsZUlkIiA6ICJjZGRiZTUyMGQwNDM0YThiYTFjYzlmYzkyZmRlMmJjZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJkYXZjaG9vIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E5MDc5MGM1N2UxODFlZDEzYWRlZDE0YzQ3ZWUyZjdjOGRlMzUzM2UwMTdiYTk1N2FmN2JkZjlkZjFiZGU5NGYiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ
# Bedrock prevents building and displaying blocks above Y127 in the Nether. # Bedrock prevents building and displaying blocks above Y127 in the Nether.
# This config option works around that by changing the Nether dimension ID to the End ID. # This config option works around that by changing the Nether dimension ID to the End ID.

Datei anzeigen

@ -0,0 +1,25 @@
# --------------------------------
# Geyser Custom Skull Configuration Files
#
# This file is ignored with `add-custom-skull-blocks` disabled.
# See `config.yml` for the main set of configuration values
#
# Custom skulls with the player username, UUID, or texture specified in this file
# will be translated as custom blocks and be displayed in the inventory and on entities.
# --------------------------------
# Java player usernames
# Skins will be updated when Geyser starts and players will have to re-download
# the resource pack if any players had changed their skin.
player-usernames:
# - GeyserMC
# Java player UUIDs
# Skins will be updated when Geyser starts and players will have to re-download
# the resource pack if any players had changed their skin.
player-uuids:
# - 8b8d8e8f-2759-47c6-acb5-5827de8a72b8
# The long string of characters found in the NBT of custom player heads
textures:
# - ewogICJ0aW1lc3RhbXAiIDogMTY1NzMyMjIzOTgzMywKICAicHJvZmlsZUlkIiA6ICJjZGRiZTUyMGQwNDM0YThiYTFjYzlmYzkyZmRlMmJjZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJkYXZjaG9vIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2E5MDc5MGM1N2UxODFlZDEzYWRlZDE0YzQ3ZWUyZjdjOGRlMzUzM2UwMTdiYTk1N2FmN2JkZjlkZjFiZGU5NGYiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ