Skip to content

Commit 3ff0307

Browse files
authored
Revert "Fix Multiplayer Mod Validation (#207)" (#221)
This reverts commit 05fe2a9.
1 parent 6f60440 commit 3ff0307

16 files changed

Lines changed: 216 additions & 272 deletions

File tree

src/test/resources/fabric.mod.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
"stationapi": "*"
6060
},
6161
"custom": {
62-
"stationapi:required_on_client": true,
63-
"stationapi:required_on_server": true
62+
"stationapi:verify_client": true
6463
}
6564
}

station-api-base/src/main/resources/fabric.mod.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
},
3737

3838
"custom": {
39-
"modmenu:api": true,
40-
"stationapi:required_on_client": true
39+
"modmenu:api": true
4140
}
4241
}

station-vanilla-checker-v0/src/main/java/net/modificationstation/stationapi/api/network/ModdedPacketHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package net.modificationstation.stationapi.api.network;
22

3-
import java.util.Map;
3+
import java.util.*;
44

55
public interface ModdedPacketHandler {
6+
67
boolean isModded();
78

89
Map<String, String> getMods();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package net.modificationstation.stationapi.impl.client.network;
2+
3+
import net.fabricmc.loader.api.FabricLoader;
4+
import net.fabricmc.loader.api.ModContainer;
5+
import net.mine_diver.unsafeevents.listener.EventListener;
6+
import net.modificationstation.stationapi.api.StationAPI;
7+
import net.modificationstation.stationapi.api.client.event.network.ServerLoginSuccessEvent;
8+
import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint;
9+
import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager;
10+
import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy;
11+
import net.modificationstation.stationapi.api.network.packet.MessagePacket;
12+
import net.modificationstation.stationapi.impl.network.ModdedPacketHandlerSetter;
13+
14+
import java.lang.invoke.MethodHandles;
15+
import java.util.*;
16+
17+
import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE;
18+
import static net.modificationstation.stationapi.api.util.Identifier.of;
19+
20+
@Entrypoint(eventBus = @EventBusPolicy(registerInstance = false))
21+
@EventListener(phase = StationAPI.INTERNAL_PHASE)
22+
public class ClientVanillaChecker {
23+
static {
24+
EntrypointManager.registerLookup(MethodHandles.lookup());
25+
}
26+
27+
@EventListener
28+
private static void handleServerLogin(ServerLoginSuccessEvent event) {
29+
List<String> splitName = Arrays.asList(event.loginHelloPacket.username.split(";"));
30+
if (splitName.contains(NAMESPACE.toString())) {
31+
MessagePacket message = new MessagePacket(of(NAMESPACE, "modlist"));
32+
List<String> mods = new ArrayList<>();
33+
mods.add(NAMESPACE.getVersion().getFriendlyString());
34+
FabricLoader.getInstance().getAllMods().stream().map(ModContainer::getMetadata).forEach(modMetadata -> Collections.addAll(mods, modMetadata.getId(), modMetadata.getVersion().getFriendlyString()));
35+
message.strings = mods.toArray(new String[0]);
36+
event.clientNetworkHandler.sendPacket(message);
37+
38+
// This definitely doesn't have a modlist entry.
39+
if(splitName.size() <= splitName.indexOf(NAMESPACE.toString()))
40+
return;
41+
42+
String modListString = splitName.get(splitName.indexOf(NAMESPACE.toString()) + 1); // Mod list string should ALWAYS follow stapi's string.
43+
44+
// If this doesn't contain stationapi, this isn't a stationapi mod list.
45+
if(!modListString.contains("stationapi="))
46+
return;
47+
48+
Map<String, String> modList = new HashMap<>();
49+
Arrays.stream(modListString.split(":")).forEach(nameVersion -> {
50+
String[] nameVersionArr = nameVersion.split("=");
51+
modList.put(nameVersionArr[0], nameVersionArr[1]);
52+
});
53+
((ModdedPacketHandlerSetter) event.clientNetworkHandler).setModded(modList);
54+
}
55+
}
56+
}

station-vanilla-checker-v0/src/main/java/net/modificationstation/stationapi/impl/network/ModListHelloPacket.java

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package net.modificationstation.stationapi.impl.network;
22

3-
import java.util.Map;
3+
import java.util.*;
44

55
public interface ModdedPacketHandlerSetter {
6-
void setModded(boolean value);
6+
77
void setModded(Map<String, String> mods);
88
}
Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package net.modificationstation.stationapi.impl.network;
22

3+
import com.google.common.hash.Hashing;
34
import net.fabricmc.loader.api.FabricLoader;
45
import net.fabricmc.loader.api.ModContainer;
56
import net.fabricmc.loader.api.metadata.ModMetadata;
67
import net.mine_diver.unsafeevents.listener.EventListener;
78
import net.modificationstation.stationapi.api.StationAPI;
8-
import net.modificationstation.stationapi.api.event.init.InitFinishedEvent;
9+
import net.modificationstation.stationapi.api.event.mod.PreInitEvent;
910
import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint;
1011
import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager;
1112
import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy;
@@ -24,32 +25,21 @@ public class VanillaChecker {
2425
EntrypointManager.registerLookup(MethodHandles.lookup());
2526
}
2627

27-
/**
28-
* A set of mods that are required client side when joining a server.
29-
*/
30-
public static final Set<ModContainer> CLIENT_REQUIRED_MODS = new HashSet<>();
28+
public static final long MASK = Hashing.sipHash24().hashUnencodedChars(NAMESPACE.toString()).asLong();
3129

3230
/**
33-
* A set of mods that are required on the server side when joining a server.
31+
* A set of mods that need client-side verification when the client joins server.
3432
*/
35-
public static final Set<ModContainer> SERVER_REQUIRED_MODS = new HashSet<>();
33+
public static final Set<ModContainer> CLIENT_REQUIRED_MODS = new HashSet<>();
3634

3735
@EventListener
38-
private static void init(InitFinishedEvent event) {
39-
LOGGER.info("Checking mod metadata for client/server-side requirements...");
40-
41-
String oldVerifyClientKey = NAMESPACE + ":verify_client";
42-
String requiredOnClientKey = NAMESPACE + ":required_on_client";
43-
String requiredOnServerKey = NAMESPACE + ":required_on_server";
36+
private static void init(PreInitEvent event) {
37+
LOGGER.info("Gathering mods that require client verification...");
38+
String value = NAMESPACE + ":verify_client";
4439
FabricLoader.getInstance().getAllMods().forEach(modContainer -> {
4540
ModMetadata modMetadata = modContainer.getMetadata();
46-
if (modMetadata.containsCustomValue(requiredOnClientKey) && modMetadata.getCustomValue(requiredOnClientKey).getAsBoolean())
47-
CLIENT_REQUIRED_MODS.add(modContainer);
48-
else if (modMetadata.containsCustomValue(oldVerifyClientKey) && modMetadata.getCustomValue(oldVerifyClientKey).getAsBoolean())
41+
if (modMetadata.containsCustomValue(value) && modMetadata.getCustomValue(value).getAsBoolean())
4942
CLIENT_REQUIRED_MODS.add(modContainer);
50-
if (modMetadata.containsCustomValue(requiredOnServerKey) && modMetadata.getCustomValue(requiredOnServerKey).getAsBoolean())
51-
SERVER_REQUIRED_MODS.add(modContainer);
5243
});
53-
LOGGER.info("Found {} mods required on client, {} mods required on server.", CLIENT_REQUIRED_MODS.size(), SERVER_REQUIRED_MODS.size());
5444
}
5545
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package net.modificationstation.stationapi.impl.server.network;
2+
3+
import net.fabricmc.loader.api.FabricLoader;
4+
import net.fabricmc.loader.api.ModContainer;
5+
import net.fabricmc.loader.api.metadata.ModMetadata;
6+
import net.mine_diver.unsafeevents.listener.EventListener;
7+
import net.minecraft.client.resource.language.I18n;
8+
import net.minecraft.entity.player.ServerPlayerEntity;
9+
import net.modificationstation.stationapi.api.StationAPI;
10+
import net.modificationstation.stationapi.api.event.registry.MessageListenerRegistryEvent;
11+
import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint;
12+
import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager;
13+
import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy;
14+
import net.modificationstation.stationapi.api.server.event.network.PlayerAttemptLoginEvent;
15+
import net.modificationstation.stationapi.impl.network.ModdedPacketHandlerSetter;
16+
17+
import java.lang.invoke.MethodHandles;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.stream.Collectors;
21+
22+
import static net.modificationstation.stationapi.api.StationAPI.LOGGER;
23+
import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE;
24+
import static net.modificationstation.stationapi.impl.network.VanillaChecker.CLIENT_REQUIRED_MODS;
25+
import static net.modificationstation.stationapi.impl.network.VanillaChecker.MASK;
26+
27+
@Entrypoint(eventBus = @EventBusPolicy(registerInstance = false))
28+
@EventListener(phase = StationAPI.INTERNAL_PHASE)
29+
public class ServerVanillaChecker {
30+
static {
31+
EntrypointManager.registerLookup(MethodHandles.lookup());
32+
}
33+
34+
@EventListener
35+
private static void onPlayerLogin(PlayerAttemptLoginEvent event) {
36+
if ((event.loginHelloPacket.worldSeed & MASK) == MASK) {
37+
Map<String, String> mods = new HashMap<>();
38+
FabricLoader.getInstance().getAllMods().forEach(modContainer -> mods.put(modContainer.getMetadata().getId(), modContainer.getMetadata().getVersion().getFriendlyString()));
39+
((ModdedPacketHandlerSetter) event.serverLoginNetworkHandler).setModded(mods);
40+
}
41+
else if (!CLIENT_REQUIRED_MODS.isEmpty()) {
42+
LOGGER.error("Player \"" + event.loginHelloPacket.username + "\" attempted joining the server without " + NAMESPACE.getName() + ", disconnecting.");
43+
event.serverLoginNetworkHandler.disconnect(I18n.getTranslation("disconnect." + NAMESPACE + ".missingStation"));
44+
}
45+
}
46+
47+
@EventListener
48+
private static void registerMessages(MessageListenerRegistryEvent event) {
49+
event.register(NAMESPACE.id("modlist"), (player, message) -> {
50+
if (!CLIENT_REQUIRED_MODS.isEmpty()) {
51+
LOGGER.info("Received a list of mods from player \"" + player.name + "\", verifying...");
52+
ServerPlayerEntity serverPlayer = (ServerPlayerEntity) player;
53+
String version = message.strings[0];
54+
String serverStationVersion = NAMESPACE.getVersion().getFriendlyString();
55+
if (!version.equals(serverStationVersion)) {
56+
LOGGER.error("Player \"" + player.name + "\" has a mismatching " + NAMESPACE.getName() + " version \"" + version + "\", disconnecting.");
57+
serverPlayer.field_255.method_833(I18n.getTranslation("disconnect." + NAMESPACE + ".stationVersionMismatch", serverStationVersion, version));
58+
return;
59+
}
60+
Map<String, String> clientMods = new HashMap<>();
61+
for (int i = 1; i < message.strings.length; i += 2)
62+
clientMods.put(message.strings[i], message.strings[i + 1]);
63+
LOGGER.info("Player \"" + player.name + "\"'s mods: " + clientMods.entrySet().stream().map(stringStringEntry -> "modid=" + stringStringEntry.getKey() + " version=" + stringStringEntry.getValue()).collect(Collectors.joining(", ", "[", "]")));
64+
String modid;
65+
String clientVersion;
66+
String serverVersion;
67+
for (ModContainer serverMod : CLIENT_REQUIRED_MODS) {
68+
ModMetadata modMetadata = serverMod.getMetadata();
69+
modid = modMetadata.getId();
70+
serverVersion = modMetadata.getVersion().getFriendlyString();
71+
if (clientMods.containsKey(modid)) {
72+
clientVersion = clientMods.get(modid);
73+
if (clientVersion == null || !clientVersion.equals(serverVersion)) {
74+
LOGGER.error("Player \"" + player.name + "\" has a mismatching " + modMetadata.getName() + " (" + modid + ")" + " version \"" + clientVersion + "\", disconnecting.");
75+
serverPlayer.field_255.method_833(I18n.getTranslation("disconnect." + NAMESPACE + ".modVersionMismatch", modMetadata.getName(), modid, serverVersion, clientVersion == null ? "null" : clientVersion));
76+
return;
77+
}
78+
} else {
79+
LOGGER.error("Player \"" + player.name + "\" has a missing mod " + modMetadata.getName() + " (" + modid + "), disconnecting.");
80+
serverPlayer.field_255.method_833(I18n.getTranslation("disconnect." + NAMESPACE + ".missingMod", modMetadata.getName(), modid, serverVersion));
81+
return;
82+
}
83+
}
84+
LOGGER.info("Player \"" + player.name + "\"'s mods have passed verification.");
85+
}
86+
});
87+
}
88+
}
Lines changed: 32 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,62 @@
11
package net.modificationstation.stationapi.mixin.network;
22

3-
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4-
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
53
import net.fabricmc.api.EnvType;
64
import net.fabricmc.api.Environment;
75
import net.fabricmc.loader.api.FabricLoader;
8-
import net.minecraft.client.Minecraft;
96
import net.minecraft.network.packet.login.LoginHelloPacket;
10-
import net.modificationstation.stationapi.api.StationAPI;
11-
import net.modificationstation.stationapi.api.network.ModdedPacketHandler;
12-
import net.modificationstation.stationapi.impl.network.ModListHelloPacket;
13-
import net.modificationstation.stationapi.mixin.network.client.ConnectScreenAccessor;
147
import org.spongepowered.asm.mixin.Mixin;
158
import org.spongepowered.asm.mixin.Shadow;
16-
import org.spongepowered.asm.mixin.Unique;
179
import org.spongepowered.asm.mixin.injection.At;
1810
import org.spongepowered.asm.mixin.injection.Constant;
1911
import org.spongepowered.asm.mixin.injection.Inject;
2012
import org.spongepowered.asm.mixin.injection.ModifyConstant;
2113
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
2214

23-
import java.io.DataInputStream;
24-
import java.io.DataOutputStream;
25-
import java.util.Arrays;
26-
import java.util.HashMap;
27-
import java.util.Map;
15+
import java.util.*;
16+
import java.util.stream.*;
17+
18+
import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE;
19+
import static net.modificationstation.stationapi.impl.network.VanillaChecker.MASK;
2820

2921
@Mixin(LoginHelloPacket.class)
30-
class LoginHelloPacketMixin implements ModListHelloPacket {
22+
class LoginHelloPacketMixin {
3123
@Shadow public String username;
32-
@Unique
33-
private HashMap<String, String> modList;
3424

35-
@Environment(EnvType.CLIENT)
36-
@WrapOperation(method = "write", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/login/LoginHelloPacket;writeString(Ljava/lang/String;Ljava/io/DataOutputStream;)V"))
37-
private void stationapi_giveTheSauce(String s, DataOutputStream dataOutputStream, Operation<Void> original) {
38-
//noinspection deprecation
39-
if (((ModdedPacketHandler) ((ConnectScreenAccessor) ((Minecraft) FabricLoader.getInstance().getGameInstance()).currentScreen).getNetworkHandler()).isModded()) {
40-
StringBuilder builder = new StringBuilder(";");
41-
FabricLoader.getInstance().getAllMods().forEach(mod -> builder.append(mod.getMetadata().getId()).append("=").append(mod.getMetadata().getVersion()).append(";"));
42-
s += builder.toString();
43-
StationAPI.LOGGER.info("Sending modlist to server.");
44-
}
45-
original.call(s, dataOutputStream);
46-
}
25+
@Shadow public long worldSeed;
4726

27+
@Inject(
28+
method = "<init>(Ljava/lang/String;IJB)V",
29+
at = @At("RETURN")
30+
)
4831
@Environment(EnvType.SERVER)
49-
@Inject(method = "read", at = @At("TAIL"))
50-
private void stationapi_readTheSauce(DataInputStream par1, CallbackInfo ci) {
51-
if (username.contains(";")) {
52-
modList = new HashMap<>();
53-
Arrays.asList(username.split(";")).forEach(modString -> {
54-
String[] mod = modString.split("=");
55-
if (mod.length == 2) {
56-
modList.put(mod[0], mod[1]);
57-
}
58-
});
59-
username = username.split(";")[0];
60-
StationAPI.LOGGER.info("Got modlist from client, containing {} mods.", modList.size());
61-
}
32+
private void stationapi_injectStAPIFlagAndModList(String username, int protocolVersion, long worldSeed, byte dimensionId, CallbackInfo ci) {
33+
this.username += NAMESPACE + ";";
34+
35+
List<String> mods = FabricLoader.getInstance().getAllMods().stream().map((modContainer -> modContainer.getMetadata().getId() + "=" + modContainer.getMetadata().getVersion().getFriendlyString())).toList();
36+
mods.forEach(mod -> this.username += mod + ":");
37+
this.username = this.username.replaceFirst(":$", "");
38+
this.username += ";";
6239
}
6340

64-
@Override
65-
public Map<String, String> stationapi_getModList() {
66-
return modList;
41+
@Inject(
42+
method = "<init>(Ljava/lang/String;I)V",
43+
at = @At("RETURN")
44+
)
45+
@Environment(EnvType.CLIENT)
46+
private void stationapi_injectStAPIFlag(String username, int protocolVersion, CallbackInfo ci) {
47+
worldSeed |= MASK;
6748
}
6849

6950
@ModifyConstant(
7051
method = "read",
71-
constant = @Constant(intValue = 16)
52+
constant = @Constant(
53+
ordinal = 0,
54+
intValue = 16
55+
),
56+
require = 0
7257
)
73-
private int stationapi_preventTheWorldFromExploding(int constant) {
58+
private int stationapi_injectHugeStringLimit(int constant) {
7459
return Short.MAX_VALUE;
7560
}
61+
7662
}

station-vanilla-checker-v0/src/main/java/net/modificationstation/stationapi/mixin/network/NetworkHandlerMixin.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,25 @@
66
import org.spongepowered.asm.mixin.Mixin;
77
import org.spongepowered.asm.mixin.Unique;
88

9-
import java.util.Map;
9+
import java.util.*;
1010

1111
@Mixin(NetworkHandler.class)
1212
class NetworkHandlerMixin implements ModdedPacketHandler, ModdedPacketHandlerSetter {
13-
@Unique
13+
1414
private Map<String, String> mods;
15-
@Unique
16-
private boolean modded = false;
1715

1816
@Override
1917
@Unique
2018
public boolean isModded() {
21-
return modded;
22-
}
23-
24-
@Override
25-
@Unique
26-
public void setModded(boolean value) {
27-
modded = value;
19+
return mods != null;
2820
}
2921

3022
@Override
3123
@Unique
3224
public void setModded(Map<String, String> mods) {
33-
modded = true;
3425
this.mods = mods;
3526
}
3627

37-
@Override
38-
@Unique
3928
public Map<String, String> getMods() {
4029
return mods;
4130
}

0 commit comments

Comments
 (0)