Skip to content
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

Implement support for Minecraft Education Edition 1.12.60 #536

Closed
wants to merge 76 commits into from

Conversation

bundabrg
Copy link
Collaborator

@bundabrg bundabrg commented May 12, 2020

Overview

This provides the following:

  • Developer support for multiple versions in the code. This makes it easier to support multiple editions using common code.
  • Support for Minecraft Education Edition 1.12.60

Notes

  • This will not compile without first compiling this PR of Protocol which will bring in 2.5.7-SNAPSHOT: Minecraft Education Edition Support CloudburstMC/Protocol#39
  • config.yml is changed to allow you to select the edition of the bedrock listener. If you wish to support multiple versions at the same time you will need to run multiple copies of GeyserMC with different config.yml files

Changes

  • Temporarily Disable Github Action as this will not build till Minecraft Education Edition Support CloudburstMC/Protocol#39 is merged
  • Add gamerule "codebuilder":"false" to disable MCEE code builder.
  • Update config.yml so that an edition is specified (bedrock or education)
  • Move away from automagic annotations to a dependency injection system. This allows versions to use the existing classes or redefine them.
  • Implement shims to allow the base codebase to have fewer changes. Any changes to the main codebase will flow into education unless it breaks and requires patching through a shim.
  • Move mappings and data resources to be namespaced by the edition (resources/edition)
  • Add SkinUtils shim to use legacy geometery data
  • Add LoginEncryptUtils shim to add a signedToken for education support. This is managed through a new TokenManager
  • Add BlockTranslator shim to handle block mappings with meta values
  • Fix respawning in Education as its Packets seem to be slightly different
  • Implement a Token Manager to handle OAuth2 tokens with both Microsoft and Minecraft servers. These are saved and updated in tokens.yml
  • Add new submodule for education mappings.

Screenshots

Screenshot from MCEE 1.12.16 to 1.15.2 PaperMC server through both ProxyPass and GeyserMC: -

IMG_20200512_084641

How to host Minecraft Education

The main differences we are interested in between MCEE and MCPE is:

  1. It runs an older slightly modified version of Bedrock.
  2. It has security by only allowing users in the same tenant to connect

Point 2 means that the server must have a valid token for a client to connect otherwise the client will fail with an error indicating that the server is not allowed to be access.

Generating a new Tenant Token

When a new MCEE tenant needs to be added use the following command:

/geyser education new

This will provide a URL that must be copy and pasted into your web browser. It starts an OAuth2 authorization flow and allows you to log into your account. Once logged in you will just get a blank page with an address in your address bar. Copy this whole address into the next step.

Now enter the following command:

/geyser education confirm {copied response}

This will go obtain the token and automatically save to tokens.yml.

The same user, and anyone else in the same tenant, can now log into the server. Multiple tenants are supported.

At no time does the server itself ever get access to the password of the user. It only gets access to an OAuth2 token that grants it access to specific minecraft resources and can renew this token indefinitely. If the user changes their password then the OAUTH token will be revoked.

Example Config

bedrock:
  # The IP address that will listen for connections
  address: 0.0.0.0
  # The port that will listen for connections
  port: 19132
  # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients
  motd1: "GeyserMC"
  motd2: "Another GeyserMC forced host."

  # Edition - bedrock or education
  edition: bedrock

...

Issues

  1. Getting the inital token is a little messy. Maybe a better way can be implemented.
  2. Users logging in with MCEE will have their Tenant username, and if supporting multiple tenants its possible for usernames to collide. Perhaps a prefix based upon the tenant needs to be added to the name or a cache of logged in users and any names that collide get suffixed with a number. If using my bleeding build which includes the plugin PR you may want to use this plugin that allows a player to enter their own username of choice at login.

Todo

Bugs

  • Crafting anything does not yet work.
  • Creativing Internal Craft is allowed at the moment when it should show barriers
  • Pressing '/' on desktop MCEE sometimes crashes the client. This may be due to too many commands which can be disabled or perhaps its a bug.

@bundabrg
Copy link
Collaborator Author

Stuff To Do (possibly not impacting this PR though):

  • Update all the mappings and provide suitable replacements for blocks/items/entities that do not exist
  • Find out why when pressing pause it crashes the client. I suspect its an NPE by the client when parsing the player list but I'm pretty sure I got that packet right. May need to somehow get a breakpoint into the client or just avoid the pause button (which sadly is next to the chat button).

@bundabrg
Copy link
Collaborator Author

bundabrg commented May 13, 2020

My docs for signedToken are wrong. You have to:

  1. Take that JWT obtained and put into https://jwt.io/ on the encoded side. Decode it to text on the right

  2. Copy the 'signedToken' field and use that

The token format is: UUID|DATESTAMP|SIGNATURE and mine was 356 characters long.

It may be worth writing a small tool that can pull this from a single MCEE device or even just doing the full authentication loop to grab it down real-time.

NOTE: Treat this token as private though its not really too bad if it gets accidently leaked (you would only be able to accept connections from MCEE in the same tenancy on that port and would not have access to the users details nor be able to use the token to do anything with the account). However I may have missed a security implication.

@lukeeey lukeeey added PR: Feature When a PR implements a new feature PR: Needs Testing When a PR needs testing but is currently not under review PR: On hold When a PR is on hold like if it requires a dependency to be updated first labels May 13, 2020
@bundabrg
Copy link
Collaborator Author

Mappings are pretty much done. Comparison screenshot:

IMG_20200512_0846412

Blocks with no documented DV I've recorded at https://github.com/bundabrg/mappings-education/issues/3 to work out.

Pause screen bug is very likely skin related. It's sending the skins of all the players and the client probably only decodes it when displaying the escape screen.

@bundabrg
Copy link
Collaborator Author

Also the player logs in with their tenant username. So likely need a config variable that allows someone to prefix connections coming in from this port or even a regex pattern that gives server owners control over how to mangle the username.

@bundabrg
Copy link
Collaborator Author

bundabrg commented May 18, 2020

Some changes:

  • Hacking at the edges to support another version was not going to work due to the number of static classes and would result in technical debt. I've instead implemented dependency injection to allow versions to be registered and to register classes associated with that version and allow packaging version specific changes. The versions should probably be separate modules but for now reside under org.geysermc.connector.edition. Instead of using static calls like "Translators.something" you instead use the convenience static call "GeyserEdition.TRANSLATORS.something" which properly looks up the registered class. This also indirectly allows 3rd party plugins (should they exist) to inject their own versions.

  • It turns out the signedTokens expire after 2 weeks so I'll be implementing an authentication flow to pull in a new one as needed in the near future.

New config looks like this:

bedrock:
  # The IP address that will listen for connections
  address: 0.0.0.0
  # The port that will listen for connections
  port: 19132
  # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients
  motd1: "GeyserMC"
  motd2: "Another GeyserMC forced host."

  # Edition - bedrock or education
  edition: bedrock

  # Version - Version number or latest for latest
  version: latest

  # Settings for education edition
  education:
    # Education edition requires a signed token. You can retrieve this by running an edu-friendly ProxyPass
    # between two MCEE devices and copying the 'jwt' from the ServerToClientHandshakePacket (everything after jwt=
    # and before the closing brace into the 'encoded' box at https://jwt.io.  From the decoded side copy the
    # value of "signedToken" into this config entry. It should be in the format UUID|UUID|DATESTAMP|SIGNATURE.
    # The token only allows clients in the same Tenant to connect. Ignore this when not using Education Mode.
    # NOTE this token will expire after 2 weeks so this will be replaced by proper authentication in the
    # near future.
    token: dead...beef

Bedrock v1.14.60 Registration

Education v1.12.60 Registration

@Camotoy
Copy link
Member

Camotoy commented May 18, 2020

So is your current code aiming for multiple versions of Bedrock connecting at the same time?

@bundabrg
Copy link
Collaborator Author

bundabrg commented May 18, 2020

No, that would be too tricky with all the static classes and how intermingled everything is. That would require a major refactor. I did look at that and started interfacing everything out but its a big job. I opted for going half way, supporting multiple versions but one per running instance.

If you want to run multiple versions you just spin up multiple Geysers with different config.yml settings on different ports. Thats how I do it (I have bedrock on port 19133, and education on port 19132)

@rtm516
Copy link
Member

rtm516 commented May 18, 2020

I think the config option for it is fine, its unlikely there will be people that want to support both editions at the same time

@Heath123
Copy link
Contributor

I think the config option for it is fine, its unlikely there will be people that want to support both editions at the same time

If they do they can have multiple instances
Do you think support for multiple instances of Geyser-Bukkit somehow could work?

@rtm516
Copy link
Member

rtm516 commented May 18, 2020

Do you think support for multiple instances of Geyser-Bukkit somehow could work?

It could work, but not sure how many changes would be needed. It might be the case of renaming the bootstrap class for the second one and recompiling it.

@Tim203
Copy link
Member

Tim203 commented May 18, 2020

its unlikely there will be people that want to support both editions at the same time

I think that if people have an option to support both that they will do that.
Atleast I would've done that if I was a server owner

@bundabrg
Copy link
Collaborator Author

bundabrg commented May 18, 2020

The only possibly reason would be when its not standalone as you can't run multiple plugins of the same name.

Due to how Bukkits ClassLoader works the plugins are not isolated so you'd have to shade it into a new path as well. So you could in theory just recompile a second plugin shaded with a different path. I can't imagine you'd need more than 2 really.

  • Rellocated is the word I meant

@bundabrg
Copy link
Collaborator Author

Something interesting that may be of interest to someone in the future. It technically is probably possible to support multiple versions/editions on the same port. Unconnected Pings can be flooded for every version (btw this should be disabled for serverhosts who likely don't want useless broadcasts) and when a player connects they send their protocol version first in the LoginPacket

This would require eliminating any static calls that have protocol side-effects and have all references via the GeyserSession which would hold the version class with references. Not something I'm aiming for here but certainly achievable and my changes here make it easier to implement.

@bundabrg
Copy link
Collaborator Author

bundabrg commented May 19, 2020

Latest change allows the following:

  • Retrieve signedTokens from the Minecraft servers as needed so they will never expire
  • Support multiple tenants on a single port. A new file tokens.yml can be created with a tenantId:refreshToken list. When a player joins their tenant is looked up in this table and an appropriate signedToken provided to allow them to connect.
  • You can use https://github.com/bundabrg/GeyserToken to retrieve an initial refreshToken to add to tokens.yml. This also means you don't even need an MCEE device (just an account that supports one) to get this token.

The way it works is that the tokens.yml file contains a list of OAuth2 refresh token for each tenant. When a signedToken is older than 7 days it is renewed, along with the refreshToken, automatically and the new refreshToken written back to this file.

The GeyserToken python script allows an initial refreshToken to be retrieved by performing an OAuth2 authentication with Microsoft (which requires a web-browser) and then provides a copy/paste line to put into tokens.yml. Once you have this initial refreshToken you no longer need the python script and this whole step can be executed from a different machine.

Copy link
Member

@rtm516 rtm516 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure on why a lot of this commit was needed as it seems to change a lot of unnecessary things
2947e1b

@rtm516
Copy link
Member

rtm516 commented May 20, 2020

Also noticed you changed the submodule locations to be version-specific, we shouldn't do this as we should always support 1 version of bedrock which should be the latest. Having the different edition folders is fine though.

@bundabrg
Copy link
Collaborator Author

Happy to drop the version part (I agree its not worth having versioned mappings yet). It was more POC to show that its easy to do and should Bedrock servers start having versioned maps easy enough to implement later anyway.

@bundabrg
Copy link
Collaborator Author

Rebased on top of master and I took the opportunity to refactor and try reduce number of changes files.

Main Changes:

  • Each Utility class now is responsible for any dependency injection and they remain static classes. This both reduces unnecessary changes to files as well as is compatible where classes are themselves instantiated.
  • I've made use of small Shim interfaces for static functions that I need to override selectively, thankfully not many.
  • Dropped the version and only an edition is now supported.

# Conflicts:
#	connector/src/main/resources/bedrock/mappings
# Conflicts:
#	connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java
#	connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
@chestnutcase
Copy link

its unlikely there will be people that want to support both editions at the same time

Just chipping in as someone from a tertiary education institution exploring Minecraft as a remote-learning education tool: yes, we would absolutely love true cross platform. There's alot of roadblocks because different stakeholders (faculty, students and IT staff) all want to use different editions for different reasons (for example, licensing EE is easier, but god forbid any kind of remote multiplayer). Keeping the backend game servers on Java while allowing any clients to connect via proxies will achieve the perfect balance of flexibility (Spigot programming to add just about anything) and client coverage.

# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
#	connector/src/main/resources/mappings
# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
#	connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java
#	connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java
#	connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
#	connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java
#	connector/src/main/resources/mappings
@bundabrg
Copy link
Collaborator Author

bundabrg commented Aug 6, 2020

Just adding a note that I'll be working on implementing MCEE support through a GeyserPlugin instead as I believe it may be easier to maintain long term. Once I have something stable I'll likely close this and link the plugin instead. This may also indirectly help support multiple Bedrock versions as the plugin allows detecting the codec version and implementing a version specific translation layer.

@Heath123
Copy link
Contributor

Heath123 commented Aug 6, 2020

Just adding a note that I'll be working on implementing MCEE support through a GeyserPlugin instead as I believe it may be easier to maintain long term. Once I have something stable I'll likely close this and link the plugin instead. This may also indirectly help support multiple Bedrock versions as the plugin allows detecting the codec version and implementing a version specific translation layer.

Do you think adding support via BedrockBackwards would work?

@bundabrg
Copy link
Collaborator Author

bundabrg commented Aug 21, 2020

Closing this in favour of the plugin. I'll provide a quick summary here:

I've now published a release of a native geyser plugin GeyserReversion. This provides support for multiversion support in Geyser as well though for now it primarily provides support for Minecraft Education v1.14.31.

This will require a build of Geyser that supports plugins. You can find one here:
https://github.com/bundabrg/Geyser/releases

GeyserReversion:
Plugin Download: https://github.com/bundabrg/GeyserReversion/releases
Github: https://github.com/bundabrg/GeyserReversion
Documentation: https://bundabrg.github.io/GeyserReversion/

Quickstart:

  1. Copy the GeyserReversion plugin jar to the plugin folder under the Geyser data folder. This folder is created automatically when running a build of Geyser that supports plugins.
  2. Start Geyser. You should notice stuff in the logs about GeyserReversion binding a port.
  3. Edit the config.yml file under plugins/GeyserReversion/config.yml and restart the server.

For the devs:

This works by providing a chain of translation layers to translate from the client to the server. Each layer is responsible for handling packets in their section and is able to inject new packets if needed. A good example is that any crafting inventory transactions require the layer to cache part of some packets and combine them with some later packets so each layer has to retain some state. We also provide some helpers to allow easier mappings for blocks, items, enchantments whilst taking care to name them correctly etc.

Please report bugs using this plugin to the Github link here and not to Geyser issues unless its reproducible without the plugin enabled.

In answer to support in BedrockBackwards unfortunately the work to get translation between layers and full state between each was greater than creating the code to simply extend BedrockServer and provide it as a pure library. The library can be found at https://github.com/bundabrg/Reversion though I've not yet had a chance to work on its documentation yet,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: Feature When a PR implements a new feature PR: Needs Testing When a PR needs testing but is currently not under review PR: On hold When a PR is on hold like if it requires a dependency to be updated first
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants