mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-23 16:59:33 +00:00
Initial work on using Minecraft gametests to test component hashing
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.platform.fabric.gametest;
|
||||||
|
|
||||||
|
import com.google.common.hash.HashCode;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
|
import net.minecraft.core.component.TypedDataComponent;
|
||||||
|
import net.minecraft.gametest.framework.GameTestHelper;
|
||||||
|
import net.minecraft.gametest.framework.GameTestInstance;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.MutableComponent;
|
||||||
|
import net.minecraft.resources.RegistryOps;
|
||||||
|
import net.minecraft.util.HashOps;
|
||||||
|
import org.geysermc.geyser.item.hashing.DataComponentHashers;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.codec.MinecraftTypes;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class GeyserComponentHashTestInstance<T> extends GeyserTestInstance {
|
||||||
|
|
||||||
|
private static final MapCodec<TypedDataComponent<?>> TYPED_COMPONENT_CODEC = DataComponentType.PERSISTENT_CODEC
|
||||||
|
.dispatchMap("component", TypedDataComponent::type, GeyserComponentHashTestInstance::typedComponentCodec);
|
||||||
|
public static final MapCodec<GeyserComponentHashTestInstance<?>> CODEC = TYPED_COMPONENT_CODEC.xmap(GeyserComponentHashTestInstance::new,
|
||||||
|
instance -> instance.testValue);
|
||||||
|
|
||||||
|
private final TypedDataComponent<T> testValue;
|
||||||
|
|
||||||
|
protected GeyserComponentHashTestInstance(TypedDataComponent<T> testValue) {
|
||||||
|
this.testValue = testValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run(@NotNull GameTestHelper helper, GeyserSession session) {
|
||||||
|
// Encode vanilla component to buffer
|
||||||
|
RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), helper.getLevel().registryAccess());
|
||||||
|
TypedDataComponent.STREAM_CODEC.encode(buffer, testValue);
|
||||||
|
|
||||||
|
// Read with MCPL
|
||||||
|
int id = MinecraftTypes.readVarInt(buffer);
|
||||||
|
DataComponent<?, ?> mcplComponent = DataComponentTypes.from(id).readDataComponent(buffer);
|
||||||
|
|
||||||
|
// Hash both and compare
|
||||||
|
RegistryOps<HashCode> ops = RegistryOps.create(HashOps.CRC32C_INSTANCE, helper.getLevel().registryAccess());
|
||||||
|
int expected = testValue.encodeValue(ops).getOrThrow().asInt();
|
||||||
|
int geyser = DataComponentHashers.hash(session, mcplComponent).asInt();
|
||||||
|
|
||||||
|
helper.assertValueEqual(expected, geyser, Component.literal("Hash for component " + testValue));
|
||||||
|
|
||||||
|
// Succeed if nothing was thrown
|
||||||
|
helper.succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull MapCodec<? extends GameTestInstance> codec() {
|
||||||
|
return CODEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NotNull MutableComponent typeDescription() {
|
||||||
|
return Component.literal("Geyser Data Component Hash Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> MapCodec<TypedDataComponent<T>> typedComponentCodec(DataComponentType<T> component) {
|
||||||
|
return component.codecOrThrow().fieldOf("value").xmap(value -> new TypedDataComponent<>(component, value), TypedDataComponent::value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.platform.fabric.gametest;
|
||||||
|
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public class GeyserFabricGametestBootstrap implements ModInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
Registry.register(BuiltInRegistries.TEST_INSTANCE_TYPE, ResourceLocation.fromNamespaceAndPath("geyser", "component_hash"), GeyserComponentHashTestInstance.CODEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.platform.fabric.gametest;
|
||||||
|
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.gametest.framework.GameTestHelper;
|
||||||
|
import net.minecraft.gametest.framework.GameTestInstance;
|
||||||
|
import net.minecraft.gametest.framework.TestData;
|
||||||
|
import net.minecraft.gametest.framework.TestEnvironmentDefinition;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class GeyserTestInstance extends GameTestInstance {
|
||||||
|
|
||||||
|
protected GeyserTestInstance() {
|
||||||
|
// TODO use default vanilla test environment
|
||||||
|
super(new TestData<>(Holder.direct(new TestEnvironmentDefinition.AllOf(List.of())),
|
||||||
|
ResourceLocation.withDefaultNamespace("empty"), 1, 1, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(@NotNull GameTestHelper helper) {
|
||||||
|
/*Map<UUID, GeyserSession> sessions = GeyserImpl.getInstance().getSessionManager().getSessions();
|
||||||
|
while (sessions.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100L);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
GeyserSession session = sessions.values().stream().findAny().orElseThrow();*/
|
||||||
|
run(helper, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void run(@NotNull GameTestHelper helper, GeyserSession session);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.platform.fabric.gametest.mixin;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.DataFixer;
|
||||||
|
import net.minecraft.gametest.framework.GameTestServer;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.Services;
|
||||||
|
import net.minecraft.server.WorldStem;
|
||||||
|
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
|
||||||
|
import net.minecraft.server.packs.repository.PackRepository;
|
||||||
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
import org.geysermc.geyser.platform.mod.GeyserServerPortGetter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
|
||||||
|
@Mixin(GameTestServer.class)
|
||||||
|
public abstract class GameTestServerMixin extends MinecraftServer implements GeyserServerPortGetter {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private static Logger LOGGER;
|
||||||
|
|
||||||
|
public GameTestServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem,
|
||||||
|
Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) {
|
||||||
|
super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int geyser$getServerPort() {
|
||||||
|
return this.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "initServer", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void startTcpServer(CallbackInfoReturnable<Boolean> callbackInfoReturnable) {
|
||||||
|
// A bit of copying from dedicated server code
|
||||||
|
InetAddress address = InetAddress.getLoopbackAddress();
|
||||||
|
if (getPort() < 0) {
|
||||||
|
setPort(25565);
|
||||||
|
}
|
||||||
|
LOGGER.info("Starting gametest server on {}:{}", address, getPort());
|
||||||
|
LOGGER.info("Geyser's tests will start once a bedrock player connects!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.getConnection().startTcpServerListener(address, getPort());
|
||||||
|
} catch (IOException exception) {
|
||||||
|
LOGGER.warn("**** FAILED TO BIND TO PORT!");
|
||||||
|
LOGGER.warn("The exception was: {}", exception.toString());
|
||||||
|
LOGGER.warn("Perhaps a server is already running on that port?");
|
||||||
|
callbackInfoReturnable.setReturnValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"type": "geyser:component_hash",
|
||||||
|
"component": "minecraft:custom_data",
|
||||||
|
"value": {
|
||||||
|
"hello": "g'day",
|
||||||
|
"nice?": false,
|
||||||
|
"coolness": 100,
|
||||||
|
"geyser": {
|
||||||
|
"is": "very cool"
|
||||||
|
},
|
||||||
|
"a list": [
|
||||||
|
["in a list"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,30 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "geyser-gametest",
|
"id": "${id}-fabric-tests",
|
||||||
"version": "1.0.0",
|
"version": "${version}",
|
||||||
"name": "Geyser-GameTest",
|
"name": "${name}-Fabric-gametest",
|
||||||
|
"description": "A bridge/proxy allowing you to connect to Minecraft: Java Edition servers with Minecraft: Bedrock Edition. ",
|
||||||
|
"authors": [
|
||||||
|
"${author}"
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"website": "${url}",
|
||||||
|
"repo": "https://github.com/GeyserMC/Geyser"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
"icon": "assets/geyser/icon.png",
|
"icon": "assets/geyser/icon.png",
|
||||||
"environment": "*",
|
"environment": "*",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"fabric-gametest": [
|
"fabric-gametest": [
|
||||||
"org.geysermc.geyser.gametest.GeyserGameTest"
|
"org.geysermc.geyser.platform.fabric.gametest.GeyserFabricGametestBootstrap"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"geyser-fabric-tests.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": ">=0.16.7",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": ">=1.21.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "org.geysermc.geyser.platform.fabric.gametest.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"mixins": [
|
||||||
|
"GameTestServerMixin"
|
||||||
|
],
|
||||||
|
"server": [],
|
||||||
|
"client": [],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,9 @@ public class GeyserFabricPlatform implements GeyserModPlatform {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable InputStream resolveResource(@NonNull String resource) {
|
public @Nullable InputStream resolveResource(@NonNull String resource) {
|
||||||
|
if (true) {
|
||||||
|
return this.getClass().getClassLoader().getResourceAsStream(resource);
|
||||||
|
}
|
||||||
// We need to handle this differently, because Fabric shares the classloader across multiple mods
|
// We need to handle this differently, because Fabric shares the classloader across multiple mods
|
||||||
Path path = this.mod.findPath(resource).orElse(null);
|
Path path = this.mod.findPath(resource).orElse(null);
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
|
|||||||
@@ -317,6 +317,10 @@ public class DataComponentHashers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <V, T extends DataComponentType<V>> HashCode hash(GeyserSession session, DataComponent<V, T> component) {
|
||||||
|
return hash(session, component.getType(), component.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
public static HashedStack hashStack(GeyserSession session, ItemStack stack) {
|
public static HashedStack hashStack(GeyserSession session, ItemStack stack) {
|
||||||
if (stack == null) {
|
if (stack == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Submodule core/src/main/resources/languages updated: 4ce8ad58ea...daa2157428
Reference in New Issue
Block a user