Initial commit
This commit is contained in:
295
src/coldfusion/hytaleserver/InventoryOwnershipGuard.java
Normal file
295
src/coldfusion/hytaleserver/InventoryOwnershipGuard.java
Normal file
@@ -0,0 +1,295 @@
|
||||
package coldfusion.hytaleserver;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Guards against inventory sharing between players (Issue #45).
|
||||
* <p>
|
||||
* This class is called by the patched LivingEntity.setInventory() method
|
||||
* to validate that an Inventory isn't being shared between multiple players.
|
||||
* <p>
|
||||
* If a shared reference is detected, the inventory is deep-cloned to break
|
||||
* the shared reference before assignment.
|
||||
*/
|
||||
public class InventoryOwnershipGuard {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger("HyFixes");
|
||||
|
||||
// Track inventory -> owner UUID
|
||||
private static final Map<Integer, UUID> inventoryOwners = new ConcurrentHashMap<>();
|
||||
private static final Map<Integer, UUID> containerOwners = new ConcurrentHashMap<>();
|
||||
|
||||
// Statistics
|
||||
private static volatile int sharedReferencesDetected = 0;
|
||||
private static volatile int inventoriesCloned = 0;
|
||||
private static volatile int validationCalls = 0;
|
||||
|
||||
// Cached reflection objects
|
||||
private static Method getPlayerRefMethod;
|
||||
private static Method getUuidMethod;
|
||||
private static Class<?> playerClass;
|
||||
private static boolean reflectionInitialized = false;
|
||||
private static boolean reflectionFailed = false;
|
||||
|
||||
/**
|
||||
* Called by patched LivingEntity.setInventory() BEFORE inventory assignment.
|
||||
*/
|
||||
public static Object validateAndClone(Object livingEntity, Object inventory) {
|
||||
if (inventory == null || livingEntity == null) {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
validationCalls++;
|
||||
|
||||
try {
|
||||
if (!reflectionInitialized && !reflectionFailed) {
|
||||
initializeReflection();
|
||||
}
|
||||
|
||||
if (reflectionFailed) {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
// Only validate for Player entities
|
||||
if (!playerClass.isInstance(livingEntity)) {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
UUID newOwnerUuid = getEntityUuid(livingEntity);
|
||||
if (newOwnerUuid == null) {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
int inventoryId = System.identityHashCode(inventory);
|
||||
UUID currentOwner = inventoryOwners.get(inventoryId);
|
||||
|
||||
if (currentOwner != null && !currentOwner.equals(newOwnerUuid)) {
|
||||
// SHARED REFERENCE DETECTED!
|
||||
sharedReferencesDetected++;
|
||||
|
||||
LOGGER.log(Level.WARNING,
|
||||
"[HyFix #45] INVENTORY SHARING DETECTED!\n" +
|
||||
" Inventory @{0} owned by {1} being assigned to {2}\n" +
|
||||
" Forcing deep clone to prevent inventory sync.",
|
||||
new Object[]{
|
||||
Integer.toHexString(inventoryId),
|
||||
currentOwner.toString().substring(0, 8) + "...",
|
||||
newOwnerUuid.toString().substring(0, 8) + "..."
|
||||
}
|
||||
);
|
||||
|
||||
Object clonedInventory = cloneInventory(inventory);
|
||||
if (clonedInventory != null) {
|
||||
inventoriesCloned++;
|
||||
int clonedId = System.identityHashCode(clonedInventory);
|
||||
inventoryOwners.put(clonedId, newOwnerUuid);
|
||||
registerContainers(clonedInventory, newOwnerUuid);
|
||||
return clonedInventory;
|
||||
} else {
|
||||
LOGGER.log(Level.SEVERE,
|
||||
"[HyFix #45] CRITICAL: Failed to clone inventory!");
|
||||
}
|
||||
} else {
|
||||
inventoryOwners.put(inventoryId, newOwnerUuid);
|
||||
registerContainers(inventory, newOwnerUuid);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (validationCalls < 5) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"[HyFix] Error in inventory validation: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return inventory;
|
||||
}
|
||||
|
||||
private static synchronized void initializeReflection() {
|
||||
if (reflectionInitialized || reflectionFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
playerClass = Class.forName("com.hypixel.hytale.server.core.entity.entities.Player");
|
||||
getPlayerRefMethod = playerClass.getMethod("getPlayerRef");
|
||||
|
||||
Class<?> playerRefClass = Class.forName("com.hypixel.hytale.server.core.universe.PlayerRef");
|
||||
getUuidMethod = playerRefClass.getMethod("getUuid");
|
||||
|
||||
reflectionInitialized = true;
|
||||
LOGGER.log(Level.INFO, "[HyFix] InventoryOwnershipGuard initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
reflectionFailed = true;
|
||||
LOGGER.log(Level.WARNING,
|
||||
"[HyFix] Failed to initialize InventoryOwnershipGuard: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID getEntityUuid(Object livingEntity) {
|
||||
try {
|
||||
Object playerRef = getPlayerRefMethod.invoke(livingEntity);
|
||||
if (playerRef == null) {
|
||||
return null;
|
||||
}
|
||||
return (UUID) getUuidMethod.invoke(playerRef);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerContainers(Object inventory, UUID ownerUuid) {
|
||||
try {
|
||||
String[] containerGetters = {"getStorage", "getArmor", "getHotbar", "getUtility", "getTools", "getBackpack"};
|
||||
|
||||
for (String getter : containerGetters) {
|
||||
try {
|
||||
Method method = inventory.getClass().getMethod(getter);
|
||||
Object container = method.invoke(inventory);
|
||||
if (container != null) {
|
||||
containerOwners.put(System.identityHashCode(container), ownerUuid);
|
||||
}
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object cloneInventory(Object inventory) {
|
||||
Object cloned = cloneViaCodec(inventory);
|
||||
if (cloned != null) {
|
||||
return cloned;
|
||||
}
|
||||
|
||||
cloned = cloneViaContainers(inventory);
|
||||
if (cloned != null) {
|
||||
return cloned;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.SEVERE, "[HyFix] All inventory clone methods failed!");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Object cloneViaCodec(Object inventory) {
|
||||
try {
|
||||
Class<?> inventoryClass = inventory.getClass();
|
||||
Field codecField = inventoryClass.getField("CODEC");
|
||||
Object codec = codecField.get(null);
|
||||
|
||||
Class<?> extraInfoClass = Class.forName("com.hypixel.hytale.codec.ExtraInfo");
|
||||
Field threadLocalField = extraInfoClass.getField("THREAD_LOCAL");
|
||||
ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
|
||||
Object extraInfo = threadLocal.get();
|
||||
|
||||
if (extraInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method encodeMethod = findMethod(codec.getClass(), "encode", Object.class, extraInfoClass);
|
||||
if (encodeMethod == null) {
|
||||
return null;
|
||||
}
|
||||
Object bsonValue = encodeMethod.invoke(codec, inventory, extraInfo);
|
||||
|
||||
Constructor<?> defaultConstructor = inventoryClass.getDeclaredConstructor();
|
||||
defaultConstructor.setAccessible(true);
|
||||
Object newInventory = defaultConstructor.newInstance();
|
||||
|
||||
Class<?> bsonValueClass = Class.forName("org.bson.BsonValue");
|
||||
Method decodeMethod = findMethod(codec.getClass(), "decode", bsonValueClass, Object.class, extraInfoClass);
|
||||
if (decodeMethod == null) {
|
||||
return null;
|
||||
}
|
||||
decodeMethod.invoke(codec, bsonValue, newInventory, extraInfo);
|
||||
|
||||
return newInventory;
|
||||
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object cloneViaContainers(Object inventory) {
|
||||
try {
|
||||
Class<?> inventoryClass = inventory.getClass();
|
||||
|
||||
Object storage = invokeGetter(inventory, "getStorage");
|
||||
Object armor = invokeGetter(inventory, "getArmor");
|
||||
Object hotbar = invokeGetter(inventory, "getHotbar");
|
||||
Object utility = invokeGetter(inventory, "getUtility");
|
||||
Object tools = invokeGetter(inventory, "getTools");
|
||||
Object backpack = invokeGetter(inventory, "getBackpack");
|
||||
|
||||
Object clonedStorage = cloneContainer(storage);
|
||||
Object clonedArmor = cloneContainer(armor);
|
||||
Object clonedHotbar = cloneContainer(hotbar);
|
||||
Object clonedUtility = cloneContainer(utility);
|
||||
Object clonedTools = cloneContainer(tools);
|
||||
Object clonedBackpack = cloneContainer(backpack);
|
||||
|
||||
Class<?> containerClass = Class.forName("com.hypixel.hytale.server.core.inventory.container.ItemContainer");
|
||||
Constructor<?> constructor = inventoryClass.getConstructor(
|
||||
containerClass, containerClass, containerClass,
|
||||
containerClass, containerClass, containerClass
|
||||
);
|
||||
|
||||
return constructor.newInstance(
|
||||
clonedStorage, clonedArmor, clonedHotbar,
|
||||
clonedUtility, clonedTools, clonedBackpack
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object cloneContainer(Object container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Method cloneMethod = container.getClass().getMethod("clone");
|
||||
return cloneMethod.invoke(container);
|
||||
} catch (Exception e) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object invokeGetter(Object obj, String methodName) {
|
||||
try {
|
||||
Method method = obj.getClass().getMethod(methodName);
|
||||
return method.invoke(obj);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
|
||||
try {
|
||||
return clazz.getMethod(name, paramTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
for (Method m : clazz.getMethods()) {
|
||||
if (m.getName().equals(name) && m.getParameterCount() == paramTypes.length) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void onPlayerDisconnect(UUID playerUuid) {
|
||||
if (playerUuid == null) {
|
||||
return;
|
||||
}
|
||||
inventoryOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
|
||||
containerOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user