-
Notifications
You must be signed in to change notification settings - Fork 418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The Mod Protocol API, aka better validation of mod presence of both-side mods with extra protocol version syncing #4011
base: 1.21.1
Are you sure you want to change the base?
Conversation
...d-protocol-api-v1/src/main/java/net/fabricmc/fabric/impl/modprotocol/ModProtocolLocator.java
Show resolved
Hide resolved
…ads, use id to get the protocol from the map
...1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java
Outdated
Show resolved
Hide resolved
...1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java
Outdated
Show resolved
Hide resolved
...1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java
Show resolved
Hide resolved
...1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java
Outdated
Show resolved
Hide resolved
...1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/ClientModProtocolLookup.java
Outdated
Show resolved
Hide resolved
fabric-mod-protocol-api-v1/src/testmod/resources/fabric.mod.json
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,42 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent appears to be messed up.
@@ -0,0 +1,8 @@ | |||
{ | |||
"text.fabric.mod_protocol.mismatched.title": "Required protocols are mismatched!", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this title is unclear, but I haven't come up with a better one 🤷🏼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbf it's best one I could think of too
PacketCodecs.map(ModProtocolResponseC2SPayload::createMap, Identifier.PACKET_CODEC, PacketCodecs.INTEGER) | ||
.xmap(ModProtocolResponseC2SPayload::new, ModProtocolResponseC2SPayload::supported).cast(); | ||
|
||
private static Object2IntMap<Identifier> createMap(int i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe there should be a cap on the number of protocols clients can send.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would require writing a custom lazy codec since the class is initialized on init, but should be doable. Client should never sent more than amount server did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be a hardcoded cap, just so that bad clients won't request a map of size 1M.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no hardcap on api side, so ideally it would be dynamic
public final class ClientModProtocolInit { | ||
public static void clientInit() { | ||
public final class ClientModProtocolInit implements ClientModInitializer { | ||
public void onInitializeClient() { | ||
ClientConfigurationNetworking.registerGlobalReceiver(ModProtocolRequestS2CPayload.ID, (payload, context) -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use @Override
.
Just wondering, wouldn't this allow any server to continuously annoy users by requesting them to install a mod? Also wondering about the wording in the original message
Does that mean that one can simply tell it (click a button) to ignore any missing mods? |
It has always been possible (and quite easy) for a server mod to write code that disconnects the player if the corresponding client side mod isn't present. This PR also won't allow server owners to request any client mod to be installed. |
Okay |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a great start, just got a few points/questions regarding the API and networking changes before looking at the impl in detail.
I think the protocols should be attached to the ModContainer
/modid in a stronger fashion, I dont think a general ID has much benefit.
I am still a little bit skeptical of using int ids as apposed to a semver version, I worry people wont remeber to change their protocol id. My inital thoughts about this problem were that we would re-use the same versioning system as used by loading when determining mod compatibility. I would expect most mod developers will just do a strict match of the specific versions, so its a setup and forget deal. I think we should discuss this and agree on the spec before spending ages perfecting the impl. See #463 for some prior thoughts on this topic.
I like having an API to query the client/server branding, I think this would be better off as a seperate PR so its not lost within this one, and could likely go in quite quickly. I would strongly suggest splitting that into another PR.
...tocol-api-v1/src/client/java/net/fabricmc/fabric/api/client/modprotocol/v1/package-info.java
Show resolved
Hide resolved
* <dt>"fabric:mod_protocol": { | ||
* "protocol": [1, 2], | ||
* "id: "custom:id", | ||
* "name": "Mod Name", | ||
* "version": "v1.2.3", | ||
* "require_client": false, | ||
* "require_server": true | ||
* }</dt> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be versioned, e.g fabric:mod_protocol_v1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah fair, through I imagine ideally it would never need incompatible format change, but can do it.
* <p>"id" is the protocols identifier, which can have any namespace and path, as long as it's valid. | ||
* It is optional and defaults to an ID with "mod" namespace and path equal to mod's id. | ||
* </p> | ||
* <p>"name" is a name displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im not sure this is that useful tbh, why would a mod have 2 names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol array support (and naming/etc) is mostly done to mirror "provides", allowing people to check against old ids when they change/include other mods protocol easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being able to change other mods protocols seems like a massive recipe for confusion. Is this not what the dependency system already handles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really for changing other mods protocol. It's mostly to mirror the "provides" functionality (your own mod changing it's id, but still being network compatible). Through to be fair it could be moved to allow this in code only
* It is optional and defaults to an ID with "mod" namespace and path equal to mod's id. | ||
* </p> | ||
* <p>"name" is a name displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.</p> | ||
* <p>"version" is a version displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, why duplicate data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
* <p>"name" is a name displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.</p> | ||
* <p>"version" is a version displayed if protocol doesn't match. It's optional and by default it uses one from mod's metadata.</p> | ||
* <p>"require_client" controls if clients without this protocol can join the server, defaults to true, preventing joining</p> | ||
* <p>"require_server" controls if clients can join servers without this mod, defaults to true, preventing joining</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is true the correct default here? I wonder if most mods would be happy to join a server without their mod 🤔 Maybe there should be no default for these values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't sure myself, but though it makes sense to default to something since the just number inline does. But can be changed
* @param protocolId protocol's id | ||
* @return Protocol version supported by the player | ||
*/ | ||
public static int getSupportedProtocol(ServerPlayerEntity player, Identifier protocolId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the usecase for getting this later on? I do agree this doesnt seem that helpful.
Im also not too keen of APIs that return -1, I think it should throw (name the method to suggest it throws) and also provide one that returns an OptionalInt
@@ -103,6 +103,11 @@ protected void handleRegistration(Identifier channelName) { | |||
protected void handleUnregistration(Identifier channelName) { | |||
} | |||
|
|||
@Override | |||
public @Nullable String getBrand() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally this wouldnt exist on the login addon, could the root method go in ClientCommonNetworkAddon
? Im not too worried about this as its impl, and can easily be refactored later. Or even sooner before this PR goes in as its own thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally I wanted to use it purely for changing the message (through ended up using the same one for everyone), so yeah I can drop this for now
/** | ||
* Gets the brand of the client the player connected with. | ||
* | ||
* @param handler the network handler, representing the connection to the player/client | ||
* @return client's brand, or {@code null} if not sent | ||
*/ | ||
@Nullable | ||
public static String getBrand(ServerConfigurationNetworkHandler handler) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are your toughts on moving this out into its own PR? Will make things a bit easier to review, this change can likely go in quite quickly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be nice to have a way to get the servers brand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing
@@ -87,6 +90,10 @@ public boolean handle(CustomPayload payload) { | |||
} | |||
} | |||
|
|||
if (payload instanceof BrandCustomPayload brand) { | |||
this.brand = brand.brand(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd return false after this point. False becuase we only need to "peek" at this packet and not consume it, allowing vanilla or any exisitng mod to also read it.
if (connection.getPacketListener() instanceof NetworkHandlerExtensions extension) { | ||
this.addon.setBrand(extension.getAddon().getBrand()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment explaing this may help.
In its current form I dont believe this supports the use case where you have an optional mod but want to have a requiment when it does exist. E.g you have a mod that is optional on the server, but the protocol must match if it does exist on the client. Care should be taken to allow a vanilla client to join in this example. |
Protocols are dettached from containers due to in api usage, which can be useful for other tools. This is why it's namespaced. Using strings would be bit more annoying when trying to use the getSupportedProtocol api's for any checks, since you might need to do a bit more matching. Aside of not all mods using valid semver in the first place. Ints are simplest way to check against version compat (which most software, including Minecraft does for protocol). My idea for improving visibility, is to put it alongside other values in |
Can you expand on this? I dont see what issues attaching them to containers/modids would cause.
I am not a massive fan of
We could easily have a fallback to support basic comparsions, such as the version being identical. Mods are highly encouraged to use semver, you already miss out on a lot of stuff and get a warning from loader if you dont.
The code for comparing versions already exists in loader, we need to make this easy for most people to use in their mods. I really dont see people updating an int every time they break protocol, it gets even more complex when you have 2 or more branches for differing minecraft versions. These problems have all been solved with semver.
I dont think we can do this, processResources is not suited to this. One solution may be to move everything to the fmj but this is another discussion.
Such as? There are complex cases that this PR in its current state doesnt handle.
I agree with this, I would expect the default to allow patch version changes. We need to find a good middle ground as like I said I dont see people every changing this int value, I know I will certainly forget. |
String ID would allow mods to define multiple protocols, though I am unsure why you need them. re: |
Can send is a just a boolean, which doesn't indicate packet version, just whatever that identifier is handled (which by itself is bit different concept, through it would require some extensions to be more useful with fabric's net api). It's pretty much similar to how network api has c:version packet, just more generic. I have similar system in polymer and I can say it's pretty useful.
"Semver" doesn't really tell much about network compatibility, as Mojang themselves break it in minor versions, but also keeps it the same if they can. I can imagine modders doing things similarly to that. To be fair I myself don't use semver correctly, just have mods that use semver-like in format. |
This api implements Mod Protocols, a simple way for a mods to quickly check if they are present on client/server, while also allowing for prevention of joining if it's missing on either side (through not being forced to). It can be also used for syncing compatible protocol versions for general networking api usage or feature toggling, without making the server/client incompatible with vanilla clients/servers.
Example screenshots of mismatch screen:
Vanilla client joining modded server:
Modded client with some mismatched mods joining modded server:
Modded client (with server required entries) joining vanilla server:
Vanillish clients get disconnected by the server, while rest is validated client side.
(Also see https://github.com/Patbox/fabric/blob/mod-protocol/fabric-mod-protocol-api-v1/src/main/java/net/fabricmc/fabric/api/modprotocol/v1/package-info.java for info)
Worth noting for people who might get confused, this is not an "anticheat"! This api allows any mod to register any protocol names and even then cheat clients could easily work around it. This api is purely to improve experience of regular users / limit amount of reports for mod devs or pack makers.
One thing to do with this pr would be bit more testing + maybe writing some unit tests, through from what I've checked everything should work. Otherwise I think it should be all done.