diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index b80757fbd..c2c17016c 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -33,6 +33,8 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.registry.type.ItemMapping; +import javax.annotation.Nonnull; + @Data public class GeyserItemStack { public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null); @@ -42,26 +44,18 @@ public class GeyserItemStack { private CompoundTag nbt; private int netId; - public GeyserItemStack(int javaId) { - this(javaId, 1); - } - - public GeyserItemStack(int javaId, int amount) { - this(javaId, amount, null); - } - - public GeyserItemStack(int javaId, int amount, CompoundTag nbt) { + private GeyserItemStack(int javaId, int amount, CompoundTag nbt) { this(javaId, amount, nbt, 1); } - public GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) { + private GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) { this.javaId = javaId; this.amount = amount; this.nbt = nbt; this.netId = netId; } - public static GeyserItemStack from(ItemStack itemStack) { + public static @Nonnull GeyserItemStack from(ItemStack itemStack) { return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt()); } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index a40161767..a9c8029a9 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -26,6 +26,9 @@ package org.geysermc.connector.inventory; import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector3i; import lombok.Getter; import lombok.NonNull; @@ -93,7 +96,7 @@ public class Inventory { public GeyserItemStack getItem(int slot) { if (slot > this.size) { - GeyserConnector.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this.toString()); + GeyserConnector.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this); return GeyserItemStack.EMPTY; } return items[slot]; @@ -101,12 +104,23 @@ public class Inventory { public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { if (slot > this.size) { - session.getConnector().getLogger().debug("Tried to set an item out of bounds! " + this.toString()); + session.getConnector().getLogger().debug("Tried to set an item out of bounds! " + this); return; } GeyserItemStack oldItem = items[slot]; updateItemNetId(oldItem, newItem, session); items[slot] = newItem; + + // Lodestone caching + if (newItem.getJavaId() == session.getItemMappings().getStoredItems().compass().getJavaId()) { + CompoundTag nbt = newItem.getNbt(); + if (nbt != null) { + Tag lodestoneTag = nbt.get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + session.getLodestoneCache().cacheInventoryItem(newItem); + } + } + } } protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 73e0d9c2e..7d500ef20 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -146,6 +146,7 @@ public class GeyserSession implements CommandSender { private EntityCache entityCache; private EntityEffectCache effectCache; private final FormCache formCache; + private final LodestoneCache lodestoneCache; private final PreferencesCache preferencesCache; private final TagCache tagCache; private WorldCache worldCache; @@ -444,6 +445,7 @@ public class GeyserSession implements CommandSender { this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); this.formCache = new FormCache(this); + this.lodestoneCache = new LodestoneCache(); this.preferencesCache = new PreferencesCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/LodestoneCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/LodestoneCache.java new file mode 100644 index 000000000..5ad07bcd2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/LodestoneCache.java @@ -0,0 +1,148 @@ +/* + * 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.connector.network.session.cache; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.geysermc.connector.inventory.GeyserItemStack; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * A temporary cache for lodestone information. + * Bedrock requests the lodestone position information separately from the item. + */ +public class LodestoneCache { + /** + * A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect + * when a new item has been created; instead we can re-use already existing IDs + */ + private final Map activeLodestones = new WeakHashMap<>(); + private final Int2ObjectMap lodestones = new Int2ObjectOpenHashMap<>(); + /** + * An ID to increment for each lodestone + */ + private int id = 1; + + public void cacheInventoryItem(GeyserItemStack itemStack) { + CompoundTag tag = itemStack.getNbt(); + CompoundTag lodestonePos = tag.get("LodestonePos"); + if (lodestonePos == null) { + // invalid + return; + } + + // Get all info needed for tracking + int x = ((IntTag) lodestonePos.get("X")).getValue(); + int y = ((IntTag) lodestonePos.get("Y")).getValue(); + int z = ((IntTag) lodestonePos.get("Z")).getValue(); + String dim = ((StringTag) tag.get("LodestoneDimension")).getValue(); + + for (LodestonePos pos : this.activeLodestones.values()) { + if (pos.equals(x, y, z, dim)) { + this.activeLodestones.put(itemStack, pos); + return; + } + } + + for (Int2ObjectMap.Entry entry : this.lodestones.int2ObjectEntrySet()) { + LodestonePos pos = entry.getValue(); + if (pos.equals(x, y, z, dim)) { + // Use this existing position instead + this.activeLodestones.put(itemStack, pos); + return; + } + } + + this.activeLodestones.put(itemStack, new LodestonePos(id++, x, y, z, dim)); + } + + public int store(CompoundTag tag) { + CompoundTag lodestonePos = tag.get("LodestonePos"); + if (lodestonePos == null) { + // invalid + return 0; + } + + // Get all info needed for tracking + int x = ((IntTag) lodestonePos.get("X")).getValue(); + int y = ((IntTag) lodestonePos.get("Y")).getValue(); + int z = ((IntTag) lodestonePos.get("Z")).getValue(); + String dim = ((StringTag) tag.get("LodestoneDimension")).getValue(); + + for (LodestonePos pos : this.activeLodestones.values()) { + if (pos.equals(x, y, z, dim)) { + // No need to add this into the lodestones map as it should not be re-requested + return pos.id; + } + } + + for (Int2ObjectMap.Entry entry : this.lodestones.int2ObjectEntrySet()) { + if (entry.getValue().equals(x, y, z, dim)) { + // Use this existing position instead + return entry.getIntKey(); + } + } + + // Start at 1 as 0 does not work + this.lodestones.put(id, new LodestonePos(id, x, y, z, dim)); + return id++; + } + + public @Nullable LodestonePos getPos(int id) { + // We should not need to check the activeLodestones map as Bedrock should already be aware of this ID + return this.lodestones.remove(id); + } + + public void clear() { + // Just in case... + this.activeLodestones.clear(); + this.lodestones.clear(); + } + + @Getter + @AllArgsConstructor + @EqualsAndHashCode + public static class LodestonePos { + private final int id; + private final int x; + private final int y; + private final int z; + private final String dimension; + + boolean equals(int x, int y, int z, String dimension) { + return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java index b8f09103d..224e12e3d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -30,24 +30,24 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBClientRequestPacket; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBServerBroadcastPacket; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.LodestoneCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.DimensionUtils; -import org.geysermc.connector.utils.LoadstoneTracker; @Translator(packet = PositionTrackingDBClientRequestPacket.class) public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTranslator { @Override public void translate(GeyserSession session, PositionTrackingDBClientRequestPacket packet) { + System.out.println(packet.toString()); PositionTrackingDBServerBroadcastPacket broadcastPacket = new PositionTrackingDBServerBroadcastPacket(); broadcastPacket.setTrackingId(packet.getTrackingId()); - // Fetch the stored Loadstone - LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(packet.getTrackingId()); + // Fetch the stored lodestone + LodestoneCache.LodestonePos pos = session.getLodestoneCache().getPos(packet.getTrackingId()); + System.out.println(pos); // If we don't have data for that ID tell the client its not found if (pos == null) { @@ -58,22 +58,20 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran broadcastPacket.setAction(PositionTrackingDBServerBroadcastPacket.Action.UPDATE); - // Build the nbt data for the update + // Build the NBT data for the update NbtMapBuilder builder = NbtMap.builder(); builder.putInt("dim", DimensionUtils.javaToBedrock(pos.getDimension())); - builder.putString("id", String.format("%08X", packet.getTrackingId())); + builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId())); builder.putByte("version", (byte) 1); // Not sure what this is for builder.putByte("status", (byte) 0); // Not sure what this is for // Build the position for the update - IntList posList = new IntArrayList(); - posList.add(pos.getX()); - posList.add(pos.getY()); - posList.add(pos.getZ()); - builder.putList("pos", NbtType.INT, posList); + builder.putList("pos", NbtType.INT, pos.getX(), pos.getY(), pos.getZ()); broadcastPacket.setTag(builder.build()); + System.out.println(broadcastPacket); + session.sendUpstreamPacket(broadcastPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index e35e5c252..13780ac49 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -34,7 +34,6 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.registry.Registries; import org.geysermc.connector.registry.type.ItemMapping; import org.geysermc.connector.registry.type.ItemMappings; -import org.geysermc.connector.utils.LoadstoneTracker; import java.util.List; import java.util.stream.Collectors; @@ -61,25 +60,7 @@ public class CompassTranslator extends ItemTranslator { if (lodestoneTag instanceof ByteTag) { // Get the fake lodestonecompass entry mapping = mappings.getStoredItems().lodestoneCompass(); - - // Get the loadstone pos - CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); - if (loadstonePos != null) { - // Get all info needed for tracking - int x = ((IntTag) loadstonePos.get("X")).getValue(); - int y = ((IntTag) loadstonePos.get("Y")).getValue(); - int z = ((IntTag) loadstonePos.get("Z")).getValue(); - String dim = ((StringTag) itemStack.getNbt().get("LodestoneDimension")).getValue(); - - // Store the info - int trackID = LoadstoneTracker.store(x, y, z, dim); - - // Set the bedrock tracking id - itemStack.getNbt().put(new IntTag("trackingHandle", trackID)); - } else { - // The loadstone was removed just set the tracking id to 0 - itemStack.getNbt().put(new IntTag("trackingHandle", 0)); - } + // NBT will be translated in nbt/LodestoneCompassTranslator } return super.translateToBedrock(itemStack, mapping, mappings); @@ -87,36 +68,12 @@ public class CompassTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { - boolean isLoadstone = false; if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { // Revert the entry back to the compass mapping = mappings.getStoredItems().compass(); - - isLoadstone = true; } - ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); - - if (isLoadstone) { - // Get the tracking id - int trackingID = ((IntTag) itemStack.getNbt().get("trackingHandle")).getValue(); - - // Fetch the tracking info from the id - LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(trackingID); - if (pos != null) { - // Build the new NBT data for the fetched tracking info - itemStack.getNbt().put(new StringTag("LodestoneDimension", pos.getDimension())); - - CompoundTag posTag = new CompoundTag("LodestonePos"); - posTag.put(new IntTag("X", pos.getX())); - posTag.put(new IntTag("Y", pos.getY())); - posTag.put(new IntTag("Z", pos.getZ())); - - itemStack.getNbt().put(posTag); - } - } - - return itemStack; + return super.translateToJava(itemData, mapping, mappings); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LodestoneCompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LodestoneCompassTranslator.java new file mode 100644 index 000000000..2696a39cf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LodestoneCompassTranslator.java @@ -0,0 +1,55 @@ +/* + * 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.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.registry.type.ItemMapping; + +@ItemRemapper +public class LodestoneCompassTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + Tag lodestoneTag = itemTag.get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + int trackId = session.getLodestoneCache().store(itemTag); + // Set the bedrock tracking id - will return 0 if invalid + itemTag.put(new IntTag("trackingHandle", trackId)); + } + } + + // NBT does not need to be translated from Bedrock Edition to Java Edition. + // translateToJava is called in three places: extra recipe loading, creative menu, and stonecutters + // Lodestone compasses cannot be touched in any of those places. + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:compass"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index b9d6ac40e..71522423c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -64,6 +64,7 @@ public class DimensionUtils { session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); session.getLecternCache().clear(); + session.getLodestoneCache().clear(); session.getSkullCache().clear(); Vector3f pos = Vector3f.from(0, Short.MAX_VALUE, 0); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java b/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java deleted file mode 100644 index bd41c34a3..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java +++ /dev/null @@ -1,83 +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.connector.utils; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -public class LoadstoneTracker { - - private static final Int2ObjectMap LOADSTONES = new Int2ObjectOpenHashMap<>(); - - /** - * Store the given coordinates and dimensions - * - * @param x The X position of the Loadstone - * @param y The Y position of the Loadstone - * @param z The Z position of the Loadstone - * @param dim The dimension containing of the Loadstone - * @return The id in the Map - */ - public static int store(int x, int y, int z, String dim) { - LoadstonePos pos = new LoadstonePos(x, y, z, dim); - - if (!LOADSTONES.containsValue(pos)) { - // Start at 1 as 0 seems to not work - LOADSTONES.put(LOADSTONES.size() + 1, pos); - } - - for (Int2ObjectMap.Entry loadstone : LOADSTONES.int2ObjectEntrySet()) { - if (loadstone.getValue().equals(pos)) { - return loadstone.getIntKey(); - } - } - - return 0; - } - - /** - * Get the loadstone data - * - * @param id The ID to get the data for - * @return The stored data - */ - public static LoadstonePos getPos(int id) { - return LOADSTONES.get(id); - } - - @Getter - @AllArgsConstructor - @EqualsAndHashCode - public static class LoadstonePos { - int x; - int y; - int z; - String dimension; - } -} \ No newline at end of file diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 2efdb453e..3a2f75a27 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 2efdb453e4e76992d63824b5c8b551bebec67b71 +Subproject commit 3a2f75a2760923ec1aa7aaf70a2f00d566ef069e