(1.3.0) Add protocol V4 support to EaglerXBungee

This commit is contained in:
lax1dude 2024-09-21 12:02:00 -07:00
parent afa7e69cfa
commit 1cf3a85c2a
91 changed files with 12133 additions and 1425 deletions

203
README_EAGLERXBUNGEE.md Normal file
View File

@ -0,0 +1,203 @@
# EaglercraftXBungee
### [`gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar`](gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar)
### "EaglerXBungee" is a plugin that allows the EaglercraftX 1.8 client to join BungeeCord servers, with an optional authentication system if online-mode is enabled. This is not a setup guide, this document is intended to be used as reference for EaglerXBungee's configuration files and provide some surface-level information meant for plugin developers.
## Compiling EaglerXBungee
Minimum JDK version is 8, as of 1.3.0 we are finally using Gradle to compile EaglerXBungee instead of compiling it all manually, however you still need to manually download the latest version of BungeeCord and name it "BungeeCord.jar" and place it in the `deps` folder first before you continue. We just don't care enough to actually use Gradle correctly to download all the dependencies automatically when they are all subject to change at any time as BungeeCord recieves updates upstream. Use the "jar" task to automatically compile the EaglerXBungee JAR file.
## Configuration Files
### NOTE: Currently, the plugin does NOT automatically update config files, if you can't find an option in one of the configuration files documented here, you most likely need to add it to the file yourself!
**The default contents of the config files for EaglerXBungee are stored in [`gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config`](gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config)**
### `settings.yml`
The settings.yml file is primarily used for configuring the built-in skin and cape service and certain connection options.
- **`server_name:`** String, default value is `'EaglercraftXBungee Server'`, sets the name of this EaglercraftX server that is sent with query responses and used for the default "404 websocket upgrade failure" page.
- **`server_uuid:`** String, default value is randomized, sets the UUID of this EaglercraftX server to send with query responses, has no official uses outside of server lists.
- **`websocket_connection_timeout:`** Number, default value is `15000` milliseconds, sets how long a WebSocket connection can last without a ping before being disconnected.
- **`websocket_handshake_timeout:`** Number, default value is `5000` milliseconds, sets how long a connection can sit in the handshake phase before being disconnected.
- **`builtin_http_server_timeout:`** Number, default value is `10000` milliseconds, sets how long an HTTP request to the built-in HTTP server can remain open before being forcefully disconnected.
- **`http_websocket_compression_level:`** Number, default value is `6`, sets the ZLIB compression level (0-9) to use for compressing websocket frames, set to 0 to disable if HTTP compression is already handled through a reverse proxy. You almost definitely need some level of compression for the game to be playable on WiFi networks.
- **`download_vanilla_skins_to_clients:`** Boolean, default value is `true`, sets if the server should download the textures of custom skulls and skins of vanilla online-mode players from Mojang's servers to cache locally and send to all EaglercraftX clients on the server that attempt to render them.
- **`valid_skin_download_urls:`** List of strings, default includes only `'textures.minecraft.net'`, sets the allowed domains to download custom skulls and skins from that are requested by EaglercraftX clients, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`uuid_lookup_ratelimit_player:`** Integer, default value is `50`, limit of how many Mojang API UUID-to-profile lookups a single player is allowed to trigger per minute, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`uuid_lookup_ratelimit_global:`** Integer, default value is `175`, limit of how many Mojang API UUID-to-profile lookups the entire server is allowed to perform per minute, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_download_ratelimit_player:`** Integer, default value is `1000`, limit of how many texture downloads a single player is allowed to trigger per minute, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_download_ratelimit_global:`** Integer, default value is `30000`, limit of how many texture downloads the entire server is allowed to perform per minute, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_db_uri:`** String, default value is `'jdbc:sqlite:eaglercraft_skins_cache.db'`, can be used to change the location of the SQLite database used as a cache for skins and profiles, or to make the server use an entirely different SQL database like MySQL to store the data instead, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_keep_objects_days:`** Integer, default value is `45`, sets the max age for textures (skin files) stored in the skin cache database, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_keep_profiles_days:`** Integer, default value is `7`, sets the max age for player profiles stored in the skin cache database, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_max_objects:`** Integer, default value is `32768`, sets the max number of textures (skin files) stored in the skin cache database before the oldest textures begin to be deleted, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_max_profiles:`** Integer, default value is `32768`, sets the max number of player profiles stored in the skin cache database before the oldest profiles begin to be deleted, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`skin_cache_antagonists_ratelimit:`** Integer, default value is `15`, sets the lockout limit for failing skin lookup requests, intended to reduce the effectiveness of some of the more simplistic types denial of service attacks that skids may attempt to perform on the skin download system, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`sql_driver_class:`** String, default value is `'internal'`, which is currently evaluated to `'org.sqlite.JDBC'`, can be used to set the name of the JDBC driver class to use for connecting to the `skin_cache_db_uri` database
- **`sql_driver_path:`** String, default value is `'internal'`, can be used to set the name of the external JAR file where the JDBC driver class to use for connecting to the `skin_cache_db_uri` database can be found, the default `'internal'` value downloads the sqlite-jdbc JAR from maven and loads it automatically, only relevant if `download_vanilla_skins_to_clients` is enabled.
- **`eagler_players_vanilla_skin:`** String, default value is `''` but was originally `'lax1dude'`, can be used to set the skin to apply to EaglercraftX players when a player on Minecraft Java Edition sees them in game. The value is the username of a premium Minecraft account to use the skin from. You cannot use a local PNG file due to the profile signature requirements in vanilla Minecraft clients.
- **`enable_is_eagler_player_property:`** Boolean, default value is `true`, can be used to control if the `isEaglerPlayer` GameProfile property should be added to EaglercraftX players, this property is used to ensure that EaglercraftX players always only display their custom skins when viewed by another EaglercraftX players on the server instead of showing the skin attached to their Java Edition username, but this property also cause plugins like ViaVersion to crash.
- **`disable_voice_chat_on_servers:`** List of strings, default value is nothing (`[]`), contains a list of names of registered servers on your BungeeCord proxy that voice chat should show up as "disabled" on. Note that to disable voice globally you should modify `listeners.yml` instead.
- **`disable_fnaw_skins_everywhere:`** Boolean, default value is `false`, can be used to globally disable FNAW skins if your players bitch about them a lot and are too lazy to just disable the FNAW skins locally on their clients.
- **`disable_fnaw_skins_on_servers:`** List of strings, default value is nothing (`[]`), contains a list of names of registered servers on your BungeeCord proxy that the FNAW skins should be disabled on. Good for explicitly disabling them for PVP but allowing them everywhere else.
- **`enable_backend_rpc_api:`** Boolean, default value is `false`, if support for servers running the EaglerXBukkitAPI plugin should be enabled or not.
### `listeners.yml`
Defines one or more "listeners" (open ports) for EaglercraftX players to use to join the server. Each listener supports the following configuration options, a lot of which you will already be familiar with if you've ever set up a BungeeCord for a Java Edition server before:
- **`address:`** String, default value is `0.0.0.0:8081`, sets the primary IPv4/port for EaglerXBungee to listen on.
- **`address_v6:`** String, default value is `'null'`, sets the primary IPv6/port for EaglerXBungee to listen on.
- **`max_players:`** Integer, default value is `60`, sets the maximum number of players that can join the server through this listener, set to `-1` to disable the limit.
- **`tab_list:`** String, default value is `GLOBAL_PING`, sets the option with the same name on the underlying BungeeCord listener, currently not used by EaglercraftX in any way.
- **`default_server:`** String, default value is `lobby`, sets the name of the default server for players to be sent to when they first connect to this listener.
- **`force_default_server:`** Boolean, default value is `false`, sets if players should always be connected to `default_server` when they connect to this listener.
- **`forward_ip:`** Boolean, default value is `false`, sets if connections to this listener will use an HTTP header to forward the player's real IP address from a reverse proxy (or CloudFlare) to the BungeeCord server. This is required for EaglerXBungee's rate limiting and a lot of plugins to work correctly if they are used behind a reverse HTTP proxy or CloudFlare.
- **`forward_ip_header:`** String, default value is `X-Real-IP`, sets the name of the request header that contains the player's real IP address if the `forward_ip` option is enabled. This option is commonly set to `X-Forwarded-For` or `CF-Connecting-IP` for a lot of server setups.
- **`redirect_legacy_clients_to:`** String, default value is `'null'`, sets the WebSocket address to redirect legacy Eaglercraft 1.5.2 clients to if they mistakenly try to join the server through this listener.
- **`server_icon:`** String, default value is `server-icon.png`, sets the name of the 64x64 PNG file to display as this listener's server icon, relative to the working directory of the BungeeCord proxy server.
- **`server_motd:`** List of up to 2 strings, default value is `'&6An EaglercraftX server'`, sets the contents of the listener's MOTD, which is the text displayed along with the `server_icon` when players add this server's listener address to their client's Multiplayer menu server list.
- **`allow_motd:`** Boolean, default value is `true`, if this listener should respond to MOTD queries or not.
- **`allow_query:`** Boolean, default value is `true`, if this listener should respond to all other types of queries or not.
- **`allow_protocol_v3:`** Boolean, default value is `true`, if this listener should allow clients using the v1/v2/v3 protocols to join (pre-u37 clients).
- **`allow_protocol_v4:`** Boolean, default value is `true`, if this listener should allow clients using the v4 protocol to join (post-u37 clients).
- **`protocol_v4_defrag_send_delay:`** Integer, default value is `10`, the number of milliseconds to wait before flushing all pending EaglercraftX plugin message packets, saves bandwidth by combining multiple messages into a single plugin message packet. Setting this to `0` has the same effect on clientbound packets as setting `eaglerNoDelay` to `true` does on a post-u37 client for all serverbound packets.
- **`allow_cookie_revoke_query:`** Boolean, default value is `true`, If this listener should accept queries from post-u37 clients to revoke session tokens, you need to create your own BungeeCord plugin to go with EaglerXBungee that handles the `EaglercraftRevokeSessionQueryEvent` event it fires in order for this feature to work correctly.
- **`request_motd_cache:`** Section that defines caching hints for server lists that cache the MOTD via the `MOTD.cache` query. As far as we know, not even the official Eaglercraft Server List on eaglercraft.com currently pays attention to these hints or attempts to cache MOTDs, so they can be ignored for now.
- **`cache_ttl:`** Integer, default value is `7200`, sets how many seconds for the server list to store the MOTD in cache.
- **`online_server_list_animation:`** Boolean, default is `false`, if the MOTD should be cached in an "animated format" that is yet to be standardized.
- **`online_server_list_results:`** Boolean, default is `true`, if the MOTD should be cached when shown in search results.
- **`online_server_list_trending:`** Boolean, default is `true`, if the MOTD should be cached if the server makes it to the top of the homepage.
- **`online_server_list_portfolios:`** Boolean, default is `false`, if the MOTD should be cached when viewing more details about the specific server.
- **`http_server:`** Section that defines settings for the integrated HTTP server, used to make the listener behave as a normal HTTP server when a non-WebSocket request is recieved (like when the listener address is entered into a browser's address bar). These options can be used to replace the "404 WebSocket Upgrade Failure" message with a custom HTML file instead.
- **`enabled:`** Boolean, default value is `false`, if this is set to true then the default "404 WebSocket Upgrade Failure" page will be disabled and replaced with the integrated file-based HTTP server, perfect for hosting a copy of the EaglercraftX client.
- **`root:`** String, default value is `web`, sets the folder that contains the HTTP server's document root, this is relative to the `plugins/EaglercraftXBungee` folder where the config files are stored.
- **`page_404_not_found:`** String, default value is `'default'`, can be used to replace the HTTP server's 404 page.
- **`page_index_name:`** List of strings, default values are `'index.html'` and `'index.htm'`, can be used to specify the name of index.html.
- **`allow_voice:`** Boolean, default is `false`, sets if voice should show up as "disabled" for players using this listener. Voice is not recommended for public servers since little to no consideration was given to actually validating the contents of signaling packets sent between clients.
- **`ratelimit:`** Section containing rate limiting configurations for several different connection types.
- **`ip:`** Global ratelimit imposed on all connection types.
- **`login:`** Sets ratelimit on login (server join) attempts.
- **`motd:`** Sets ratelimit on MOTD query types.
- **`query:`** Sets ratelimit on all other query types.
- **`enable:`** If the rate limit (ip/login/motd/query) should be enabled.
- **`period:`** Sets the period in the number of seconds.
- **`limit:`** Sets the number of requests a single IP address can send in `period` seconds before being limited.
- **`limit_lockout:`** Sets the number of requests a single IP address can send in `period` seconds before being locked out.
- **`lockout_duration:`** Sets the total number of seconds a "lock out" should last on this limiter.
### `authservice.yml`
The authservice.yml file is used for configuring the built-in online mode authentication service included with the plugin or to integrate with a 3rd party authentication system provided by another plugin.
- **`enable_authentication_system:`** Boolean, default is `true`, if the events for the authentication protocol should be enabled.
- **`use_onboard_eaglerx_system:`** Boolean, default is `true`, if the built-in online mode authentication system should be enabled.
- **`auth_db_uri:`** String, default value is `'jdbc:sqlite:eaglercraft_auths.db'`, can be used to change the location of the SQLite database used for the built-in online mode authentication system, or to make the server use an entirely different SQL database like MySQL to store the data instead.
- **`sql_driver_class:`** String, default value is `'internal'`, see the description of the `settings.yml` option with the same name.
- **`sql_driver_path:`** String, default value is `'internal'`, see the description of the `settings.yml` option with the same name.
- **`password_prompt_screen_text:`** String, default value is `'Enter your password to join:'`, text displayed on the EaglercraftX client's password screen when joining the server with the built-in online mode authentication system.
- **`wrong_password_screen_text:`** String, default value is `'Password Incorrect!'`, text displayed if the wrong password is entered on the EaglercraftX client's password screen when joining the server with the built-in online mode authentication system.
- **`not_registered_screen_text:`** String, default value is `'You are not registered on this server!'`, text displayed when joining the server with the built-in online mode authentication system when using an account that has not been registered.
- **`eagler_command_name:`** String, default value is `'eagler'`, the name of the command to use for registering and/or logging in when joining the server with the built-in online mode authentication system.
- **`use_register_command_text:`** String, default value is `'&aUse /eagler to set an Eaglercraft password on this account'`, localization for when players use the `/eagler` command on the server.
- **`use_change_command_text:`** String, default value is `'&bUse /eagler to change your Eaglercraft password'`, localization for when players use the `/eagler` command on the server.
- **`command_success_text:`** String, default value is `'&bYour eagler password was changed successfully.'`, localization for when players use the `/eagler` command on the server.
- **`last_eagler_login_message:`** String, default value is `'Your last Eaglercraft login was on $date from $ip'`, localization for when players join the server with the built-in online mode authentication system.
- **`too_many_registrations_message:`** String, default value is `'&cThe maximum number of registrations has been reached for your IP address'`, localization for when players use the `/eagler` command on the server.
- **`need_vanilla_to_register_message:`** String, default value is `'&cYou need to log in with a vanilla account to use this command'`, localization for when players use the `/eagler` command on the server.
- **`override_eagler_to_vanilla_skins:`** Boolean, default value is `false`, if players who join the server after registering with an online mode account should show the same skin as the online-mode account they registered with.
- **`max_registration_per_ip:`** Integer, default value is `-1`, if greater than 0 it specifies the max number of accounts that can be created per IP address on the server with the built-in online mode authentication system.
### `ice_servers.yml`
The ice_servers.yml file is used for configuring the set of STUN/TURN servers that clients on this server should use for voice chat. Beware the default "openrelayproject" TURN servers are no longer active as of 2024, most likely as a result of being the default ond only TURN servers shipped with every copy of Eaglercraft to ever use WebRTC in some way.
- **`voice_servers_no_passwd:`** List of strings, defines a set of STUN/TURN server URIs to use that don't require a username and password.
- **`voice_servers_passwd:`** Section of sections, defines a set of STUN/TURN server URIs to use that do require a username and password, along with the username and password to use with each one.
### `updates.yml`
The updates.yml file is used for configuring the decentralized and totally legal update system used by EaglercraftX clients.
- **`block_all_client_updates:`** Boolean, default value is `false`, can be used to completely disable the update system.
- **`discard_login_packet_certs:`** Boolean, default value is `false`, can be used to prevent the server from relaying random crowdsourced update certificates that were recieved from players who joined the server using signed clients.
- **`cert_packet_data_rate_limit:`** Integer, default value is `524288`, can be used to set the global rate limit for how many bytes per second of certificates the server should send to all players.
- **`enable_eagcert_folder:`** Boolean, default value is `true`, can be used to enable or disable the "eagcert" folder used for distributing specific certificates as locally provided .cert files
- **`download_latest_certs:`** Boolean, default value is `true`, can be used to automaticlly download the latest certificates to the "eagcert" folder
- **`download_certs_from:`** List of strings, defines the URLs to download the certificates from if `download_latest_certs` is enabled
- **`check_for_update_every:`** Integer, default value is `28800` seconds, defines how often to check the URL list for updated certificates
### `pause_menu/pause_menu.yml`
For EaglercraftX u37 and up, can be used for changing the appearance of the pause menu and a few other screens with custom icons and strings, also used for enabling the "Server Info" webview and configuring its contents.
- **`enable_custom_pause_menu:`** Boolean, default value is `false`, if pause menu customization should be enabled on supported clients or not
- **`server_info_button:`** Section, defines properties of the "Server Info" button, which is always hidden unless pause menu customization is enabled
- **`enable_button:`** Boolean, default value is `true`, if the button should be shown or not
- **`button_text:`** String, default value is `'Server Info'`, the text to display on the button, useful if you want to use this feature for something other than a "Server Info" button
- **`button_mode_open_new_tab:`** Boolean, default value is `false`, can be used to make the "Server Info" button act as a hyperlink that opens a URL in a new tab instead of displaying content in an embedded webview iframe in the client.
- **`server_info_embed_url:`** String, default value is `''`, sets the URL for the "Server Info" button to use if it should open a URL in a new tab or the webview instead of directly downloading the markup to display from the EaglerXBungee server itself over the WebSocket.
- **`button_mode_embed_file:`** Boolean, default value is `true`, determines if the "Server Info" button should download the webview markup directly from the EaglerXBungee server over WebSocket instead of loading an external URL. Cannot be used with `button_mode_open_new_tab`!
- **`server_info_embed_file:`** String, default value is `'server_info.html'`, sets the name of the local file/template containing the markup to display in the "Server Info" webview if it is not in URL mode.
- **`server_info_embed_screen_title:`** String, default value is `'Server Info'`, sets the title string of the screen that displays the webview.
- **`server_info_embed_send_chunk_rate:`** Integer, default value is `1`, defines how many chunks of server info data to send per 250ms when downloading the server info markup to a client.
- **`server_info_embed_send_chunk_size:`** Integer, default value is `24576`, defines the size of each chunk of server info data when it is being downloaded to a client.
- **`enable_template_macros:`** Boolean, default value is `true`, if the server info markup should be processed for any eagler template macros (defined like `` {% arg1 `arg2` ... %} ``)
- **`server_info_embed_template_globals:`** Section, defines a list of additional global variables to use with the template processor
- **`allow_embed_template_eval_macro:`** Boolean, default value is `false`, if the template processor should allow the "eval" macro to be used in the server info markup file (not to be confused with the JavaScript function, although there is never a good reason to use JavaScript's eval function in your code either)
- **`enable_webview_javascript:`** Boolean, default value is `false`, if the server info webview should allow JavaScript to be executed or not. This will display an "allow JavaScript" screen to your players the first time they attempt to view it.
- **`enable_webview_message_api:`** Boolean, default value is `false`, if the server info webview has JavaScript enabled and should be permitted to open a message channel back to your EaglerXBungee server to exchange arbitrary message packets. This can be used, for example, to implement a dynamic menu on your server using JavaScript and HTML that players can access through the server info webview that integrates directly with your gamemodes.
- **`enable_webview_strict_csp:`** Boolean, default value is `true`, if the `csp` attribute on the webview iframe should be set or not for added security, beware this is not supported on all browsers and will be silently disabled when the client detects it as unsupported.
- **`discord_button:`** Section, can be used to turn the "Invite" (formerly "Open to LAN") button on the pause menu into a "Discord" button that players can click to join your discord server
- **`enable_button:`** Boolean, default value is `true`, sets if the discord button should be enabled or not
- **`button_text:`** String, default value is `'Discord'`, sets the text that should be displayed on the button
- **`button_url:`** String, default value is `'https://invite url here'`, defines the URL to open when the button is pressed
- **`custom_images:`** Section, can be used to add icons to certain buttons, change the backgrounds of some screens, and add watermarks of your server to the inventory and pause menu and such if you're into that. For the best GPU compatibility, use only textures with powers of 2 as dimensions (such as 32x32 pixels), the width and height do not need to match, they just both need to be a power of 2. There is also a limit on the maximum size, icons can be no larger than 255x255 pixels (Effective max power-of-2 is 128x128). Color values will be downsampled to 16bpp and use a magic value to represent transparent pixels when the pause menu customization packet is sent to a client.
- **`icon_title_L:`** String, default value is `''`, sets the icon to show on the left side of the pause menu screen's title
- **`icon_title_R:`** String, default value is `''`, sets the icon to show on the right side of the pause menu screen's title
- **`icon_backToGame_L:`** String, default value is `''`, sets the icon to show on the left side of the "Back to Game" button
- **`icon_backToGame_R:`** String, default value is `''`, sets the icon to show on the right side of the "Back to Game" button
- **`icon_achievements_L:`** String, default value is `''`, sets the icon to show on the left side of the "Achievements" button
- **`icon_achievements_R:`** String, default value is `''`, sets the icon to show on the right side of the "Achievements" button
- **`icon_statistics_L:`** String, default value is `''`, sets the icon to show on the left side of the "Statistics" button
- **`icon_statistics_R:`** String, default value is `''`, sets the icon to show on the right side of the "Statistics" button
- **`icon_serverInfo_L:`** String, default value is `''`, sets the icon to show on the left side of the server info button
- **`icon_serverInfo_R:`** String, default value is `''`, sets the icon to show on the right side of the server info button
- **`icon_options_L:`** String, default value is `''`, sets the icon to show on the left side of the "Options" button
- **`icon_options_R:`** String, default value is `''`, sets the icon to show on the right side of the "Options" button
- **`icon_discord_L:`** String, default value is `''`, sets the icon to show on the left side of the discord button
- **`icon_discord_R:`** String, default value is `''`, sets the icon to show on the right side of the discord button
- **`icon_disconnect_L:`** String, default value is `''`, sets the icon to show on the left side of the "Disconnect" button
- **`icon_disconnect_R:`** String, default value is `''`, sets the icon to show on the right side of the "Disconnect" button
- **`icon_background_pause:`** String, default value is `'test_image.png'`, sets the icon to show as a repeating pattern in the background of the pause menu and related screens. It is especially important for GPU compatibility for this one to be a power-of-2 sized texture.
- **`icon_background_all:`** String, default value is `'test_image.png'`, sets the icon to show as a repeating pattern in the background of all other screens in the game. It is especially important for GPU compatibility for this one to be a power-of-2 sized texture.
- **`icon_watermark_pause:`** String, default value is `''`, sets a watermark to show in the bottom-left corner of the pause menu
- **`icon_watermark_all:`** String, default value is `''`, sets a watermark to show in the bottom-left corner of all other screens in the game
## Event Types
The events added by EaglerXBungee are located in the [`net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event`](gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/api/event) package and can be listened for the same way as you would for a regular BungeeCord event. When an EaglercraftX 1.8 player joins your server, all the regular BungeeCord login events are fired by EaglerXBungee to maintain compatibility with other existing BungeeCord plugins, however EaglerXBungee also adds several of its own event types to allow additional Eaglercraft specific features to be accessible through the main BungeeCord event bus as well.
- **`EaglercraftWebSocketOpenEvent`** Event that is fired when a new WebSocket connection is first opened to the server (regardless if its a query or actual player login attempt) useful for quickly filtering out new connections based on a specific origin or user agent header.
- **`EaglercraftClientBrandEvent`** Event that is fired when an EaglercraftX player joins, it contains the Eaglercraft client's "brand" and "version" strings along with the origin and user agent headers that can be used to detect 90% of the currently existing skid clients thanks to lax1dude making the base client self-snitch these commonly modified strings.
- **`EaglercraftIsAuthRequiredEvent`** Event that is fired when an EaglercraftX player attempts to join the server while the authentication system is enabled and `use_onboard_eaglerx_system` is `false`, used for implementing custom authentication systems.
- **`EaglercraftHandleAuthPasswordEvent`** Event that is fired when an EaglercraftX player enters a password into the "Authentication Required" screen while the authentication system is enabled and `use_onboard_eaglerx_system` is `false`, used for implementing custom authentication systems.
- **`EaglercraftHandleAuthCookieEvent`** Event that is fired when an EaglercraftX player joins the server with cookies set and while authentication system is enabled and `use_onboard_eaglerx_system` is `false`, you must set cookie auth as allowed while handling "EaglercraftIsAuthRequiredEvent" first for this event to actually be fired, used for implementing custom authentication systems that use cookies to store a session token for auto login.
- **`EaglercraftRevokeSessionQueryEvent`** Event that is fired when a player uses the "Revoke Session Token" feature in a u37 client to invalidate a cookie that was set on their client with the "revoke query supported" bit. Make sure to enable session revoke queries in listeners.yml!
- **`EaglercraftRegisterSkinEvent`** Event that is fired when an EaglercraftX player's skin is recieved, can be used to analyze or modify or replace the skin with a different texture or preset ID if needed. Note that players using pre-u37 clients may not see the modified/replaced skin.
- **`EaglercraftRegisterCapeEvent`** Event that is fired when an EaglercraftX player's cape is recieved, can be used to analyze or modify or replace the cape with a different texture or preset ID if needed. Use preset ID 0 to disable their cape entirely. Note that players using pre-u37 clients may not see the modified/replaced cape.
- **`EaglercraftMOTDEvent`** Event that is fired when a MOTD query request is recieved, used for implement a custom server MOTD handler, or implementing an animated MOTD like the EaglerMOTD plugin.
- **`EaglercraftVoiceStatusChangeEvent`** Event that is fired when `allow_voice` is enabled and a player transitions between voice states (SERVER_DISABLE, DISABLED, ENABLED) cannot be cancelled so it is mostly just useful for logging or displaying some "Rules" in chat.
- **`EaglercraftWebViewChannelEvent`** Event that is fired when the server info webview is open and JavaScript is enabled and the webview opens/closes a new message channel to EaglerXBungee.
- **`EaglercraftWebViewMessageEvent`** Event that is fired when the server info webview is open and JavaScript is enabled and the webview has already opened a channel to EaglerXBungee and a new message is recieved on that open channel.
## Registering Queries
If you would like to add your own custom `Accept:` query handlers to the proxy (along with MOTD, version, and session revoke) you can register them at startup using the register functions provided by the [`EaglerQueryHandler`](gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/api/query/EaglerQueryHandler.java) class.
## EaglerXBungeeAPIHelper
To help make plugin development easier, a class called [`EaglerXBungeeAPIHelper`](gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/api/EaglerXBungeeAPIHelper.java) is included that defines dozens of helper functions for easily and safely interacting with EaglercraftX clients. This is to enable developers to program plugins for EaglerXBungee servers with minimal knowledge of the actual underlying protocol used by the client for skins and capes and voice and other exclusive features. Its recommended to convert all your existing code to use the `EaglerXBungeeAPIHelper` instead of whatever packet hacks you were doing before it was added when you migrate your network to EaglerXBungee 1.3.0+. All your existing packet hacks will be broken anyway unless you configure the server to force all u37+ clients to use protocol V3 instead.

View File

@ -2,3 +2,10 @@ lib/*
.idea/* .idea/*
*.iml *.iml
out/* out/*
deps/BungeeCord.jar
/.gradle/
/.settings/
.classpath
.project
/build/
/bin/

View File

@ -0,0 +1,43 @@
plugins {
id 'java'
id 'eclipse'
}
group = 'net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord'
version = ''
repositories {
mavenCentral()
}
sourceSets {
main {
java {
srcDirs(
'src/main/java',
'../../sources/protocol-game/java',
'../backend-rpc-protocol/src/backend-rpc-protocol/java'
)
}
}
}
dependencies {
implementation files('deps/BungeeCord.jar')
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
jar {
compileJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8'
manifest {
attributes(
'Main-Class': 'net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.MainClass'
)
}
}

View File

@ -0,0 +1,3 @@
Place the BungeeCord JAR file in this folder, name it "BungeeCord.jar"
You can download it from here: https://ci.md-5.net/job/BungeeCord/

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Jun 20 10:14:47 CDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gateway/EaglercraftXBungee/gradlew vendored Normal file
View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gateway/EaglercraftXBungee/gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,2 @@
rootProject.name = 'EaglercraftXBungee'

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.logging.Level; import java.util.logging.Level;
@ -15,7 +16,10 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandClientBrand;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandConfirmCode; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandConfirmCode;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandDomain; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandDomain;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandEaglerPurge; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandEaglerPurge;
@ -27,6 +31,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerList
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPacketEventListener; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPacketEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPluginEventListener; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPluginEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipeline; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerUpdateSvc;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.CompatWarning; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.CompatWarning;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient;
@ -35,7 +40,9 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ISkinServic
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinServiceOffline; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
@ -57,7 +64,7 @@ import net.md_5.bungee.BungeeCord;
*/ */
public class EaglerXBungee extends Plugin { public class EaglerXBungee extends Plugin {
public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:cda4537:1851"; public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:acb85e3:1871";
public static final String NATIVE_WATERFALL_BUILD = "1.21-R0.1-SNAPSHOT:de8345a:579"; public static final String NATIVE_WATERFALL_BUILD = "1.21-R0.1-SNAPSHOT:de8345a:579";
static { static {
@ -72,6 +79,7 @@ public class EaglerXBungee extends Plugin {
private Timer closeInactiveConnections = null; private Timer closeInactiveConnections = null;
private Timer skinServiceTasks = null; private Timer skinServiceTasks = null;
private Timer authServiceTasks = null; private Timer authServiceTasks = null;
private Timer updateServiceTasks = null;
private final ChannelFutureListener newChannelListener; private final ChannelFutureListener newChannelListener;
private ISkinService skinService; private ISkinService skinService;
private CapeServiceOffline capeService; private CapeServiceOffline capeService;
@ -80,7 +88,7 @@ public class EaglerXBungee extends Plugin {
public EaglerXBungee() { public EaglerXBungee() {
instance = this; instance = this;
openChannels = new LinkedList(); openChannels = new LinkedList<>();
newChannelListener = new ChannelFutureListener() { newChannelListener = new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture ch) throws Exception { public void operationComplete(ChannelFuture ch) throws Exception {
@ -99,6 +107,12 @@ public class EaglerXBungee extends Plugin {
@Override @Override
public void onLoad() { public void onLoad() {
Map<String, String> templateGlobals = EaglerXBungeeAPIHelper.getTemplateGlobals();
PluginDescription desc = this.getDescription();
templateGlobals.put("plugin_name", desc.getName());
templateGlobals.put("plugin_version", desc.getVersion());
templateGlobals.put("plugin_authors", desc.getAuthor());
templateGlobals.put("plugin_description", desc.getDescription());
try { try {
eventLoopGroup = ((BungeeCord) getProxy()).eventLoops; eventLoopGroup = ((BungeeCord) getProxy()).eventLoops;
} catch (NoSuchFieldError e) { } catch (NoSuchFieldError e) {
@ -120,6 +134,7 @@ public class EaglerXBungee extends Plugin {
mgr.registerCommand(this, new CommandRatelimit()); mgr.registerCommand(this, new CommandRatelimit());
mgr.registerCommand(this, new CommandConfirmCode()); mgr.registerCommand(this, new CommandConfirmCode());
mgr.registerCommand(this, new CommandDomain()); mgr.registerCommand(this, new CommandDomain());
mgr.registerCommand(this, new CommandClientBrand());
EaglerAuthConfig authConf = conf.getAuthConfig(); EaglerAuthConfig authConf = conf.getAuthConfig();
conf.setCracked(!BungeeCord.getInstance().getConfig().isOnlineMode() || !authConf.isEnableAuthentication()); conf.setCracked(!BungeeCord.getInstance().getConfig().isOnlineMode() || !authConf.isEnableAuthentication());
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) { if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
@ -131,11 +146,11 @@ public class EaglerXBungee extends Plugin {
mgr.registerCommand(this, new CommandEaglerPurge(authConf.getEaglerCommandName())); mgr.registerCommand(this, new CommandEaglerPurge(authConf.getEaglerCommandName()));
} }
} }
getProxy().registerChannel(SkinService.CHANNEL); for(String str : GamePluginMessageProtocol.getAllChannels()) {
getProxy().registerChannel(CapeServiceOffline.CHANNEL); getProxy().registerChannel(str);
getProxy().registerChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); }
getProxy().registerChannel(VoiceService.CHANNEL); getProxy().registerChannel(EaglerBackendRPCProtocol.CHANNEL_NAME);
getProxy().registerChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL); getProxy().registerChannel(EaglerBackendRPCProtocol.CHANNEL_NAME_READY);
getProxy().registerChannel(EaglerPacketEventListener.GET_DOMAIN_CHANNEL); getProxy().registerChannel(EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
startListeners(); startListeners();
if(closeInactiveConnections != null) { if(closeInactiveConnections != null) {
@ -206,6 +221,23 @@ public class EaglerXBungee extends Plugin {
}else { }else {
logger().info("Voice chat disabled, add \"allow_voice: true\" to your listeners to enable"); logger().info("Voice chat disabled, add \"allow_voice: true\" to your listeners to enable");
} }
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
if(!conf.getUpdateConfig().isBlockAllClientUpdates()) {
updateServiceTasks = new Timer("EaglerXBungee: Update Service Tasks");
updateServiceTasks.schedule(new TimerTask() {
@Override
public void run() {
try {
EaglerUpdateSvc.updateTick();
}catch(Throwable t) {
logger().log(Level.SEVERE, "Error ticking update service!", t);
}
}
}, 0l, 5000l);
}
} }
@Override @Override
@ -213,11 +245,12 @@ public class EaglerXBungee extends Plugin {
PluginManager mgr = getProxy().getPluginManager(); PluginManager mgr = getProxy().getPluginManager();
mgr.unregisterListeners(this); mgr.unregisterListeners(this);
mgr.unregisterCommands(this); mgr.unregisterCommands(this);
getProxy().unregisterChannel(SkinService.CHANNEL); for(String str : GamePluginMessageProtocol.getAllChannels()) {
getProxy().unregisterChannel(CapeServiceOffline.CHANNEL); getProxy().unregisterChannel(str);
getProxy().unregisterChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); }
getProxy().unregisterChannel(VoiceService.CHANNEL); getProxy().unregisterChannel(EaglerBackendRPCProtocol.CHANNEL_NAME);
getProxy().unregisterChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL); getProxy().unregisterChannel(EaglerBackendRPCProtocol.CHANNEL_NAME_READY);
getProxy().unregisterChannel(EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
stopListeners(); stopListeners();
if(closeInactiveConnections != null) { if(closeInactiveConnections != null) {
closeInactiveConnections.cancel(); closeInactiveConnections.cancel();
@ -227,6 +260,10 @@ public class EaglerXBungee extends Plugin {
skinServiceTasks.cancel(); skinServiceTasks.cancel();
skinServiceTasks = null; skinServiceTasks = null;
} }
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
skinService.shutdown(); skinService.shutdown();
skinService = null; skinService = null;
capeService.shutdown(); capeService.shutdown();

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public enum EnumVoiceState {
SERVER_DISABLE,
DISABLED,
ENABLED;
}

View File

@ -0,0 +1,23 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public enum EnumWebViewState {
NOT_SUPPORTED,
SERVER_DISABLE,
CHANNEL_CLOSED,
CHANNEL_OPEN;
}

View File

@ -0,0 +1,362 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG.EnumBadgePriority;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class NotificationBadgeBuilder {
public static enum BadgePriority {
LOW, NORMAL, HIGHER, HIGHEST;
}
private UUID badgeUUID = null;
private BaseComponent bodyComponent = null;
private BaseComponent titleComponent = null;
private BaseComponent sourceComponent = null;
private long originalTimestampSec = 0l;
private boolean silent = false;
private BadgePriority priority = BadgePriority.NORMAL;
private UUID mainIconUUID = null;
private UUID titleIconUUID = null;
private int hideAfterSec = 10;
private int expireAfterSec = 3600;
private int backgroundColor = 0xFFFFFF;
private int bodyTxtColor = 0xFFFFFF;
private int titleTxtColor = 0xFFFFFF;
private int sourceTxtColor = 0xFFFFFF;
private SPacketNotifBadgeShowV4EAG packetCache = null;
private boolean packetDirty = true;
public NotificationBadgeBuilder() {
originalTimestampSec = System.currentTimeMillis() / 1000l;
}
public NotificationBadgeBuilder(NotificationBadgeBuilder builder) {
badgeUUID = builder.badgeUUID;
bodyComponent = builder.bodyComponent;
titleComponent = builder.titleComponent;
sourceComponent = builder.sourceComponent;
originalTimestampSec = builder.originalTimestampSec;
silent = builder.silent;
priority = builder.priority;
mainIconUUID = builder.mainIconUUID;
titleIconUUID = builder.titleIconUUID;
hideAfterSec = builder.hideAfterSec;
backgroundColor = builder.backgroundColor;
bodyTxtColor = builder.bodyTxtColor;
titleTxtColor = builder.titleTxtColor;
sourceTxtColor = builder.sourceTxtColor;
packetCache = !builder.packetDirty ? builder.packetCache : null;
packetDirty = builder.packetDirty;
}
public NotificationBadgeBuilder(SPacketNotifBadgeShowV4EAG packet) {
badgeUUID = new UUID(packet.badgeUUIDMost, packet.badgeUUIDLeast);
try {
bodyComponent = ComponentSerializer.deserialize(packet.bodyComponent);
}catch(Throwable t) {
bodyComponent = new TextComponent(packet.bodyComponent);
}
try {
titleComponent = ComponentSerializer.deserialize(packet.titleComponent);
}catch(Throwable t) {
titleComponent = new TextComponent(packet.titleComponent);
}
try {
sourceComponent = ComponentSerializer.deserialize(packet.sourceComponent);
}catch(Throwable t) {
sourceComponent = new TextComponent(packet.sourceComponent);
}
originalTimestampSec = packet.originalTimestampSec;
silent = packet.silent;
switch(packet.priority) {
case LOW:
default:
priority = BadgePriority.LOW;
break;
case NORMAL:
priority = BadgePriority.NORMAL;
break;
case HIGHER:
priority = BadgePriority.HIGHER;
break;
case HIGHEST:
priority = BadgePriority.HIGHEST;
break;
}
mainIconUUID = new UUID(packet.mainIconUUIDMost, packet.mainIconUUIDLeast);
titleIconUUID = new UUID(packet.titleIconUUIDMost, packet.titleIconUUIDLeast);
hideAfterSec = packet.hideAfterSec;
backgroundColor = packet.backgroundColor;
bodyTxtColor = packet.bodyTxtColor;
titleTxtColor = packet.titleTxtColor;
sourceTxtColor = packet.sourceTxtColor;
packetCache = packet;
packetDirty = false;
}
public UUID getBadgeUUID() {
return badgeUUID;
}
public NotificationBadgeBuilder setBadgeUUID(UUID badgeUUID) {
this.badgeUUID = badgeUUID;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBadgeUUIDRandom() {
this.badgeUUID = UUID.randomUUID();
this.packetDirty = true;
return this;
}
public BaseComponent getBodyComponent() {
return bodyComponent;
}
public NotificationBadgeBuilder setBodyComponent(BaseComponent bodyComponent) {
this.bodyComponent = bodyComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBodyComponent(String bodyText) {
this.bodyComponent = new TextComponent(bodyText);
this.packetDirty = true;
return this;
}
public BaseComponent getTitleComponent() {
return titleComponent;
}
public NotificationBadgeBuilder setTitleComponent(BaseComponent titleComponent) {
this.titleComponent = titleComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setTitleComponent(String titleText) {
this.titleComponent = new TextComponent(titleText);
this.packetDirty = true;
return this;
}
public BaseComponent getSourceComponent() {
return sourceComponent;
}
public NotificationBadgeBuilder setSourceComponent(BaseComponent sourceComponent) {
this.sourceComponent = sourceComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setSourceComponent(String sourceText) {
this.sourceComponent = new TextComponent(sourceText);
this.packetDirty = true;
return this;
}
public long getOriginalTimestampSec() {
return originalTimestampSec;
}
public NotificationBadgeBuilder setOriginalTimestampSec(long originalTimestampSec) {
this.originalTimestampSec = originalTimestampSec;
this.packetDirty = true;
return this;
}
public boolean isSilent() {
return silent;
}
public NotificationBadgeBuilder setSilent(boolean silent) {
this.silent = silent;
this.packetDirty = true;
return this;
}
public BadgePriority getPriority() {
return priority;
}
public NotificationBadgeBuilder setPriority(BadgePriority priority) {
this.priority = priority;
this.packetDirty = true;
return this;
}
public UUID getMainIconUUID() {
return mainIconUUID;
}
public NotificationBadgeBuilder setMainIconUUID(UUID mainIconUUID) {
this.mainIconUUID = mainIconUUID;
this.packetDirty = true;
return this;
}
public UUID getTitleIconUUID() {
return titleIconUUID;
}
public NotificationBadgeBuilder setTitleIconUUID(UUID titleIconUUID) {
this.titleIconUUID = titleIconUUID;
this.packetDirty = true;
return this;
}
public int getHideAfterSec() {
return hideAfterSec;
}
public NotificationBadgeBuilder setHideAfterSec(int hideAfterSec) {
this.hideAfterSec = hideAfterSec;
this.packetDirty = true;
return this;
}
public int getExpireAfterSec() {
return expireAfterSec;
}
public NotificationBadgeBuilder setExpireAfterSec(int expireAfterSec) {
this.expireAfterSec = expireAfterSec;
this.packetDirty = true;
return this;
}
public int getBackgroundColor() {
return backgroundColor;
}
public NotificationBadgeBuilder setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
this.packetDirty = true;
return this;
}
public int getBodyTxtColorRGB() {
return bodyTxtColor;
}
public NotificationBadgeBuilder setBodyTxtColorRGB(int colorRGB) {
this.bodyTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBodyTxtColorRGB(int colorR, int colorG, int colorB) {
this.bodyTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public int getTitleTxtColorRGB() {
return titleTxtColor;
}
public NotificationBadgeBuilder setTitleTxtColorRGB(int colorRGB) {
this.titleTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setTitleTxtColorRGB(int colorR, int colorG, int colorB) {
this.titleTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public int getSourceTxtColorRGB() {
return sourceTxtColor;
}
public NotificationBadgeBuilder setSourceTxtColorRGB(int colorRGB) {
this.sourceTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setSourceTxtColorRGB(int colorR, int colorG, int colorB) {
this.sourceTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public Object clone() {
return new NotificationBadgeBuilder(this);
}
public SPacketNotifBadgeShowV4EAG buildPacket() {
if(packetDirty || packetCache == null) {
if(badgeUUID == null) {
badgeUUID = UUID.randomUUID();
}else if(badgeUUID.getMostSignificantBits() == 0l && badgeUUID.getLeastSignificantBits() == 0l) {
throw new IllegalStateException("Badge UUID cannot be 0!");
}
EnumBadgePriority internalPriority;
switch(priority) {
case LOW:
default:
internalPriority = EnumBadgePriority.LOW;
break;
case NORMAL:
internalPriority = EnumBadgePriority.NORMAL;
break;
case HIGHER:
internalPriority = EnumBadgePriority.HIGHER;
break;
case HIGHEST:
internalPriority = EnumBadgePriority.HIGHEST;
break;
}
String bodyComp = bodyComponent != null ? ComponentSerializer.toString(bodyComponent) : "";
if(bodyComp.length() > 32767) {
throw new IllegalStateException("Body component is longer than 32767 chars serialized!");
}
String titleComp = titleComponent != null ? ComponentSerializer.toString(titleComponent) : "";
if(titleComp.length() > 255) {
throw new IllegalStateException("Title component is longer than 255 chars serialized!");
}
String sourceComp = sourceComponent != null ? ComponentSerializer.toString(sourceComponent) : "";
if(sourceComp.length() > 255) {
throw new IllegalStateException("Body component is longer than 255 chars serialized!");
}
packetCache = new SPacketNotifBadgeShowV4EAG(badgeUUID.getMostSignificantBits(),
badgeUUID.getLeastSignificantBits(), bodyComp, titleComp, sourceComp, originalTimestampSec, silent,
internalPriority, mainIconUUID != null ? mainIconUUID.getMostSignificantBits() : 0l,
mainIconUUID != null ? mainIconUUID.getLeastSignificantBits() : 0l,
titleIconUUID != null ? titleIconUUID.getMostSignificantBits() : 0l,
titleIconUUID != null ? titleIconUUID.getLeastSignificantBits() : 0l, hideAfterSec, expireAfterSec,
backgroundColor, bodyTxtColor, titleTxtColor, sourceTxtColor);
packetDirty = false;
}
return packetCache;
}
}

View File

@ -0,0 +1,92 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftClientBrandEvent extends Event implements Cancellable {
private final String clientBrand;
private final String clientVersion;
private final String origin;
private final int protocolVersion;
private final InetAddress remoteAddress;
private boolean cancelled;
private BaseComponent message;
public EaglercraftClientBrandEvent(String clientBrand, String clientVersion, String origin, int protocolVersion,
InetAddress remoteAddress) {
this.clientBrand = clientBrand;
this.clientVersion = clientVersion;
this.origin = origin;
this.protocolVersion = protocolVersion;
this.remoteAddress = remoteAddress;
}
public BaseComponent getMessage() {
return message;
}
public void setMessage(BaseComponent message) {
this.message = message;
}
public String getClientBrand() {
return clientBrand;
}
public String getClientVersion() {
return clientVersion;
}
public String getOrigin() {
return origin;
}
public int getProtocolVersion() {
return protocolVersion;
}
public InetAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public void setKickMessage(String message) {
this.cancelled = true;
this.message = new TextComponent(message);
}
public void setKickMessage(BaseComponent message) {
this.cancelled = true;
this.message = message;
}
}

View File

@ -0,0 +1,206 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftHandleAuthCookieEvent extends Event {
public static enum AuthResponse {
ALLOW, DENY, REQUIRE_AUTH
}
private final EaglerListenerConfig listener;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final boolean enableCookies;
private final byte[] cookieData;
private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod;
private final String eventAuthMessage;
private final Object authAttachment;
private AuthResponse eventResponse;
private byte[] authUsername;
private String authProfileUsername;
private UUID authProfileUUID;
private String authRequestedServerRespose;
private String authDeniedMessage = "Bad Cookie!";
private String applyTexturesPropValue;
private String applyTexturesPropSignature;
private boolean overrideEaglerToVanillaSkins;
private Consumer<EaglercraftHandleAuthCookieEvent> continueThread;
private Runnable continueRunnable;
private volatile boolean hasContinue = false;
public EaglercraftHandleAuthCookieEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress,
String authOrigin, byte[] authUsername, String authProfileUsername, UUID authProfileUUID,
boolean enableCookies, byte[] cookieData, EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod,
String eventAuthMessage, Object authAttachment, String authRequestedServerRespose,
Consumer<EaglercraftHandleAuthCookieEvent> continueThread) {
this.listener = listener;
this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin;
this.authUsername = authUsername;
this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
this.eventAuthMethod = eventAuthMethod;
this.eventAuthMessage = eventAuthMessage;
this.authAttachment = authAttachment;
this.authRequestedServerRespose = authRequestedServerRespose;
this.continueThread = continueThread;
}
public EaglerListenerConfig getListener() {
return listener;
}
public InetAddress getRemoteAddress() {
return authRemoteAddress;
}
public String getOriginHeader() {
return authOrigin;
}
public boolean getCookiesEnabled() {
return enableCookies;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return cookieData != null ? new String(cookieData, StandardCharsets.UTF_8) : null;
}
public byte[] getAuthUsername() {
return authUsername;
}
public String getProfileUsername() {
return authProfileUsername;
}
public void setProfileUsername(String username) {
this.authProfileUsername = username;
}
public UUID getProfileUUID() {
return authProfileUUID;
}
public void setProfileUUID(UUID uuid) {
this.authProfileUUID = uuid;
}
public EaglercraftIsAuthRequiredEvent.AuthMethod getAuthType() {
return eventAuthMethod;
}
public String getAuthMessage() {
return eventAuthMessage;
}
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
public String getAuthRequestedServer() {
return authRequestedServerRespose;
}
public void setAuthRequestedServer(String server) {
this.authRequestedServerRespose = server;
}
public void setLoginAllowed() {
this.eventResponse = AuthResponse.ALLOW;
this.authDeniedMessage = null;
}
public void setLoginPasswordRequired() {
this.eventResponse = AuthResponse.REQUIRE_AUTH;
this.authDeniedMessage = null;
}
public void setLoginDenied(String message) {
this.eventResponse = AuthResponse.DENY;
this.authDeniedMessage = message;
}
public AuthResponse getLoginAllowed() {
return eventResponse;
}
public String getLoginDeniedMessage() {
return authDeniedMessage;
}
public Runnable makeAsyncContinue() {
if(continueRunnable == null) {
continueRunnable = new Runnable() {
@Override
public void run() {
if(!hasContinue) {
hasContinue = true;
continueThread.accept(EaglercraftHandleAuthCookieEvent.this);
}else {
throw new IllegalStateException("Thread was already continued from a different function! Auth plugin conflict?");
}
}
};
}
return continueRunnable;
}
public boolean isAsyncContinue() {
return continueRunnable != null;
}
public void doDirectContinue() {
continueThread.accept(this);
}
public void applyTexturesProperty(String value, String signature) {
applyTexturesPropValue = value;
applyTexturesPropSignature = signature;
}
public String getApplyTexturesPropertyValue() {
return applyTexturesPropValue;
}
public String getApplyTexturesPropertySignature() {
return applyTexturesPropSignature;
}
public void setOverrideEaglerToVanillaSkins(boolean overrideEaglerToVanillaSkins) {
this.overrideEaglerToVanillaSkins = overrideEaglerToVanillaSkins;
}
public boolean isOverrideEaglerToVanillaSkins() {
return overrideEaglerToVanillaSkins;
}
}

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -34,12 +35,14 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
private final byte[] authUsername; private final byte[] authUsername;
private final byte[] authSaltingData; private final byte[] authSaltingData;
private final byte[] authPasswordData; private final byte[] authPasswordData;
private final boolean enableCookies;
private final byte[] cookieData;
private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod; private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod;
private final String eventAuthMessage; private final String eventAuthMessage;
private final Object authAttachment; private final Object authAttachment;
private AuthResponse eventResponse; private AuthResponse eventResponse;
private CharSequence authProfileUsername; private String authProfileUsername;
private UUID authProfileUUID; private UUID authProfileUUID;
private String authRequestedServerRespose; private String authRequestedServerRespose;
private String authDeniedMessage = "Password Incorrect!"; private String authDeniedMessage = "Password Incorrect!";
@ -51,10 +54,10 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
private volatile boolean hasContinue = false; private volatile boolean hasContinue = false;
public EaglercraftHandleAuthPasswordEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress, public EaglercraftHandleAuthPasswordEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress,
String authOrigin, byte[] authUsername, byte[] authSaltingData, CharSequence authProfileUsername, String authOrigin, byte[] authUsername, byte[] authSaltingData, String authProfileUsername,
UUID authProfileUUID, byte[] authPasswordData, EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod, UUID authProfileUUID, byte[] authPasswordData, boolean enableCookies, byte[] cookieData,
String eventAuthMessage, Object authAttachment, String authRequestedServerRespose, EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod, String eventAuthMessage, Object authAttachment,
Consumer<EaglercraftHandleAuthPasswordEvent> continueThread) { String authRequestedServerRespose, Consumer<EaglercraftHandleAuthPasswordEvent> continueThread) {
this.listener = listener; this.listener = listener;
this.authRemoteAddress = authRemoteAddress; this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin; this.authOrigin = authOrigin;
@ -63,6 +66,8 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
this.authProfileUsername = authProfileUsername; this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID; this.authProfileUUID = authProfileUUID;
this.authPasswordData = authPasswordData; this.authPasswordData = authPasswordData;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
this.eventAuthMethod = eventAuthMethod; this.eventAuthMethod = eventAuthMethod;
this.eventAuthMessage = eventAuthMessage; this.eventAuthMessage = eventAuthMessage;
this.authAttachment = authAttachment; this.authAttachment = authAttachment;
@ -90,11 +95,23 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
return authSaltingData; return authSaltingData;
} }
public CharSequence getProfileUsername() { public boolean getCookiesEnabled() {
return enableCookies;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return cookieData != null ? new String(cookieData, StandardCharsets.UTF_8) : null;
}
public String getProfileUsername() {
return authProfileUsername; return authProfileUsername;
} }
public void setProfileUsername(CharSequence username) { public void setProfileUsername(String username) {
this.authProfileUsername = username; this.authProfileUsername = username;
} }

View File

@ -7,7 +7,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerList
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -53,6 +53,7 @@ public class EaglercraftIsAuthRequiredEvent extends Event {
private String eventAuthMessage = "enter the code:"; private String eventAuthMessage = "enter the code:";
private String kickUserMessage = "Login Denied"; private String kickUserMessage = "Login Denied";
private Object authAttachment; private Object authAttachment;
private boolean enableCookieAuth;
private Consumer<EaglercraftIsAuthRequiredEvent> continueThread; private Consumer<EaglercraftIsAuthRequiredEvent> continueThread;
private Runnable continueRunnable; private Runnable continueRunnable;
private volatile boolean hasContinue = false; private volatile boolean hasContinue = false;
@ -117,6 +118,14 @@ public class EaglercraftIsAuthRequiredEvent extends Event {
this.authAttachment = authAttachment; this.authAttachment = authAttachment;
} }
public boolean getEnableCookieAuth() {
return enableCookieAuth;
}
public void setEnableCookieAuth(boolean enable) {
this.enableCookieAuth = enable;
}
public boolean shouldKickUser() { public boolean shouldKickUser() {
return authResponse == null || authResponse == AuthResponse.DENY; return authResponse == null || authResponse == AuthResponse.DENY;
} }

View File

@ -2,6 +2,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.util.UUID; import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
/** /**
@ -21,13 +22,15 @@ import net.md_5.bungee.api.plugin.Event;
*/ */
public class EaglercraftRegisterCapeEvent extends Event { public class EaglercraftRegisterCapeEvent extends Event {
private final Object authAttachment;
private final String username; private final String username;
private final UUID uuid; private final UUID uuid;
private byte[] customTex = null; private byte[] customTex = null;
public EaglercraftRegisterCapeEvent(String username, UUID uuid) { public EaglercraftRegisterCapeEvent(String username, UUID uuid, Object authAttachment) {
this.username = username; this.username = username;
this.uuid = uuid; this.uuid = uuid;
this.authAttachment = authAttachment;
} }
public String getUsername() { public String getUsername() {
@ -47,10 +50,13 @@ public class EaglercraftRegisterCapeEvent extends Event {
customTex[4] = (byte)(p & 0xFF); customTex[4] = (byte)(p & 0xFF);
} }
/**
* @param tex raw 32x32 pixel RGBA texture (4096 bytes long), see capes in "sources/resources/assets/eagler/capes"
*/
public void setForceUseCustom(byte[] tex) { public void setForceUseCustom(byte[] tex) {
customTex = new byte[1 + tex.length]; customTex = new byte[1174];
customTex[0] = (byte)2; customTex[0] = (byte)2;
System.arraycopy(tex, 0, customTex, 1, tex.length); EaglerXBungeeAPIHelper.convertCape32x32RGBAto23x17RGB(tex, 0, customTex, 1);
} }
public void setForceUseCustomByPacket(byte[] packet) { public void setForceUseCustomByPacket(byte[] packet) {
@ -60,4 +66,8 @@ public class EaglercraftRegisterCapeEvent extends Event {
public byte[] getForceSetUseCustomPacket() { public byte[] getForceSetUseCustomPacket() {
return customTex; return customTex;
} }
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
} }

View File

@ -22,30 +22,29 @@ import net.md_5.bungee.protocol.Property;
*/ */
public class EaglercraftRegisterSkinEvent extends Event { public class EaglercraftRegisterSkinEvent extends Event {
private final Object authAttachment;
private final String username; private final String username;
private final UUID uuid; private final UUID uuid;
private Property useMojangProfileProperty = null; private Property useMojangProfileProperty = null;
private boolean useLoginResultTextures = false; private boolean useLoginResultTextures = false;
private byte[] customTex = null; private byte[] customTex = null;
private String customURL = null;
public EaglercraftRegisterSkinEvent(String username, UUID uuid) { public EaglercraftRegisterSkinEvent(String username, UUID uuid, Object authAttachment) {
this.username = username; this.username = username;
this.uuid = uuid; this.uuid = uuid;
this.authAttachment = authAttachment;
} }
public void setForceUseMojangProfileProperty(Property prop) { public void setForceUseMojangProfileProperty(Property prop) {
useMojangProfileProperty = prop; useMojangProfileProperty = prop;
useLoginResultTextures = false; useLoginResultTextures = false;
customTex = null; customTex = null;
customURL = null;
} }
public void setForceUseLoginResultObjectTextures(boolean b) { public void setForceUseLoginResultObjectTextures(boolean b) {
useMojangProfileProperty = null; useMojangProfileProperty = null;
useLoginResultTextures = b; useLoginResultTextures = b;
customTex = null; customTex = null;
customURL = null;
} }
public void setForceUsePreset(int p) { public void setForceUsePreset(int p) {
@ -57,9 +56,11 @@ public class EaglercraftRegisterSkinEvent extends Event {
customTex[2] = (byte)(p >>> 16); customTex[2] = (byte)(p >>> 16);
customTex[3] = (byte)(p >>> 8); customTex[3] = (byte)(p >>> 8);
customTex[4] = (byte)(p & 0xFF); customTex[4] = (byte)(p & 0xFF);
customURL = null;
} }
/**
* @param tex raw 64x64 pixel RGBA texture (16384 bytes long)
*/
public void setForceUseCustom(int model, byte[] tex) { public void setForceUseCustom(int model, byte[] tex) {
useMojangProfileProperty = null; useMojangProfileProperty = null;
useLoginResultTextures = false; useLoginResultTextures = false;
@ -67,21 +68,12 @@ public class EaglercraftRegisterSkinEvent extends Event {
customTex[0] = (byte)2; customTex[0] = (byte)2;
customTex[1] = (byte)model; customTex[1] = (byte)model;
System.arraycopy(tex, 0, customTex, 2, tex.length); System.arraycopy(tex, 0, customTex, 2, tex.length);
customURL = null;
} }
public void setForceUseCustomByPacket(byte[] packet) { public void setForceUseCustomByPacket(byte[] packet) {
useMojangProfileProperty = null; useMojangProfileProperty = null;
useLoginResultTextures = false; useLoginResultTextures = false;
customTex = packet; customTex = packet;
customURL = null;
}
public void setForceUseURL(String url) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
customTex = null;
customURL = url;
} }
public String getUsername() { public String getUsername() {
@ -104,8 +96,8 @@ public class EaglercraftRegisterSkinEvent extends Event {
return customTex; return customTex;
} }
public String getForceSetUseURL() { public <T> T getAuthAttachment() {
return customURL; return (T)authAttachment;
} }
} }

View File

@ -0,0 +1,91 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.RevokeSessionQueryHandler;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftRevokeSessionQueryEvent extends Event {
private final InetAddress remoteAddress;
private final String origin;
private final byte[] cookieData;
private final RevokeSessionQueryHandler queryHandler;
private EnumSessionRevokeStatus revokeStatus;
private boolean shouldDeleteCookie;
public static enum EnumSessionRevokeStatus {
SUCCESS("ok", -1), FAILED_NOT_SUPPORTED("error", 1), FAILED_NOT_ALLOWED("error", 2),
FAILED_NOT_FOUND("error", 3), FAILED_SERVER_ERROR("error", 4);
public final String status;
public final int code;
private EnumSessionRevokeStatus(String status, int code) {
this.status = status;
this.code = code;
}
}
public EaglercraftRevokeSessionQueryEvent(InetAddress remoteAddress, String origin, byte[] cookieData,
RevokeSessionQueryHandler queryHandler) {
this.remoteAddress = remoteAddress;
this.origin = origin;
this.cookieData = cookieData;
this.queryHandler = queryHandler;
this.revokeStatus = EnumSessionRevokeStatus.FAILED_NOT_SUPPORTED;
this.shouldDeleteCookie = false;
}
public InetAddress getRemoteAddress() {
return remoteAddress;
}
public String getOrigin() {
return origin;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return new String(cookieData, StandardCharsets.UTF_8);
}
public RevokeSessionQueryHandler getQuery() {
return queryHandler;
}
public void setResultStatus(EnumSessionRevokeStatus revokeStatus) {
this.revokeStatus = revokeStatus;
}
public EnumSessionRevokeStatus getResultStatus() {
return revokeStatus;
}
public boolean getShouldDeleteCookie() {
return shouldDeleteCookie;
}
public void setShouldDeleteCookie(boolean b) {
this.shouldDeleteCookie = b;
}
}

View File

@ -0,0 +1,64 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftVoiceStatusChangeEvent extends Event {
public static enum EnumVoiceState {
SERVER_DISABLE, DISABLED, ENABLED;
}
private final ProxiedPlayer playerObj;
private final EaglerListenerConfig listener;
private final EaglerInitialHandler eaglerHandler;
private final EnumVoiceState voiceStateOld;
private final EnumVoiceState voiceStateNew;
public EaglercraftVoiceStatusChangeEvent(ProxiedPlayer playerObj, EaglerListenerConfig listener,
EaglerInitialHandler eaglerHandler, EnumVoiceState voiceStateOld, EnumVoiceState voiceStateNew) {
this.playerObj = playerObj;
this.listener = listener;
this.eaglerHandler = eaglerHandler;
this.voiceStateOld = voiceStateOld;
this.voiceStateNew = voiceStateNew;
}
public ProxiedPlayer getPlayerObj() {
return playerObj;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerInitialHandler getEaglerHandler() {
return eaglerHandler;
}
public EnumVoiceState getVoiceStateOld() {
return voiceStateOld;
}
public EnumVoiceState getVoiceStateNew() {
return voiceStateNew;
}
}

View File

@ -0,0 +1,70 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftWebSocketOpenEvent extends Event implements Cancellable {
private final Channel channel;
private final EaglerListenerConfig listener;
private final String realIP;
private final String origin;
private final String userAgent;
private boolean cancelled = false;
public EaglercraftWebSocketOpenEvent(Channel channel, EaglerListenerConfig listener, String realIP, String origin, String userAgent) {
this.channel = channel;
this.listener = listener;
this.realIP = realIP;
this.origin = origin;
this.userAgent = userAgent;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean var1) {
cancelled = var1;
}
public Channel getChannel() {
return channel;
}
public EaglerListenerConfig getListener() {
return listener;
}
public String getRealIP() {
return realIP;
}
public String getOrigin() {
return origin;
}
public String getUserAgent() {
return userAgent;
}
}

View File

@ -0,0 +1,56 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftWebViewChannelEvent extends Event {
public static enum EventType {
CHANNEL_OPEN, CHANNEL_CLOSE;
}
private final ProxiedPlayer player;
private final EaglerListenerConfig listener;
private final String channel;
private final EventType type;
public EaglercraftWebViewChannelEvent(ProxiedPlayer player, EaglerListenerConfig listener, String channel, EventType type) {
this.player = player;
this.listener = listener;
this.channel = channel;
this.type = type;
}
public ProxiedPlayer getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public String getChannel() {
return channel;
}
public EventType getType() {
return type;
}
}

View File

@ -0,0 +1,118 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftWebViewMessageEvent extends Event {
public static enum MessageType {
STRING(SPacketWebViewMessageV4EAG.TYPE_STRING), BINARY(SPacketWebViewMessageV4EAG.TYPE_BINARY);
private final int id;
private MessageType(int id) {
this.id = id;
}
private static MessageType fromId(int id) {
switch(id) {
case CPacketWebViewMessageV4EAG.TYPE_STRING:
return STRING;
default:
case CPacketWebViewMessageV4EAG.TYPE_BINARY:
return BINARY;
}
}
}
private final ProxiedPlayer player;
private final EaglerListenerConfig listener;
private final String currentChannel;
private final EaglerInitialHandler eaglerHandler;
private final MessageType type;
private final byte[] data;
private String asString;
public EaglercraftWebViewMessageEvent(ProxiedPlayer player, EaglerListenerConfig listener, String currentChannel, MessageType type, byte[] data) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXBungeeAPIHelper.getEaglerHandle(player);
this.type = type;
this.data = data;
}
public EaglercraftWebViewMessageEvent(ProxiedPlayer player, EaglerListenerConfig listener, String currentChannel, CPacketWebViewMessageV4EAG packet) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXBungeeAPIHelper.getEaglerHandle(player);
this.type = MessageType.fromId(packet.type);
this.data = packet.data;
}
public ProxiedPlayer getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerInitialHandler getEaglerHandle() {
return eaglerHandler;
}
public void sendResponse(MessageType type, byte[] data) {
eaglerHandler.sendEaglerMessage(new SPacketWebViewMessageV4EAG(type.id, data));
}
public void sendResponse(String str) {
sendResponse(MessageType.STRING, str.getBytes(StandardCharsets.UTF_8));
}
public void sendResponse(byte[] data) {
sendResponse(MessageType.BINARY, data);
}
public MessageType getType() {
return type;
}
public byte[] getAsBinary() {
return data;
}
public String getAsString() {
if(asString == null) {
asString = new String(data, StandardCharsets.UTF_8);
}
return asString;
}
public String getChannelName() {
return currentChannel;
}
}

View File

@ -3,6 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List; import java.util.List;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
/** /**
@ -31,7 +32,7 @@ public interface MOTDConnection {
long getConnectionTimestamp(); long getConnectionTimestamp();
public default long getConnectionAge() { public default long getConnectionAge() {
return System.currentTimeMillis() - getConnectionTimestamp(); return EaglerXBungeeAPIHelper.steadyTimeMillis() - getConnectionTimestamp();
} }
void sendToUser(); void sendToUser();

View File

@ -5,6 +5,8 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
* *
@ -28,7 +30,7 @@ public class AuthLoadingCache<K, V> {
private V instance; private V instance;
private CacheEntry(V instance) { private CacheEntry(V instance) {
this.lastHit = System.currentTimeMillis(); this.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
this.instance = instance; this.instance = instance;
} }
@ -49,7 +51,7 @@ public class AuthLoadingCache<K, V> {
private long cacheTimer; private long cacheTimer;
public AuthLoadingCache(CacheLoader<K, V> provider, long cacheTTL) { public AuthLoadingCache(CacheLoader<K, V> provider, long cacheTTL) {
this.cacheMap = new HashMap(); this.cacheMap = new HashMap<>();
this.provider = provider; this.provider = provider;
this.cacheTTL = cacheTTL; this.cacheTTL = cacheTTL;
} }
@ -66,7 +68,7 @@ public class AuthLoadingCache<K, V> {
} }
return loaded; return loaded;
}else { }else {
etr.lastHit = System.currentTimeMillis(); etr.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
return etr.instance; return etr.instance;
} }
} }
@ -90,7 +92,7 @@ public class AuthLoadingCache<K, V> {
} }
public void tick() { public void tick() {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(millis - cacheTimer > (cacheTTL / 2L)) { if(millis - cacheTimer > (cacheTTL / 2L)) {
cacheTimer = millis; cacheTimer = millis;
synchronized(cacheMap) { synchronized(cacheMap) {

View File

@ -24,6 +24,7 @@ import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent;
@ -249,7 +250,7 @@ public class DefaultAuthSystem {
this.checkRegistrationByName = databaseConnection.prepareStatement("SELECT Version, MojangUUID, MojangTextures, HashBase, HashSalt, Registered, RegisteredIP, LastLogin, LastLoginIP FROM eaglercraft_accounts WHERE MojangUsername = ?"); this.checkRegistrationByName = databaseConnection.prepareStatement("SELECT Version, MojangUUID, MojangTextures, HashBase, HashSalt, Registered, RegisteredIP, LastLogin, LastLoginIP FROM eaglercraft_accounts WHERE MojangUsername = ?");
this.setLastLogin = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET LastLogin = ?, LastLoginIP = ? WHERE MojangUUID = ?"); this.setLastLogin = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET LastLogin = ?, LastLoginIP = ? WHERE MojangUUID = ?");
this.updateTextures = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET MojangTextures = ? WHERE MojangUUID = ?"); this.updateTextures = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET MojangTextures = ? WHERE MojangUUID = ?");
this.authLoadingCache = new AuthLoadingCache(new AccountLoader(), 120000l); this.authLoadingCache = new AuthLoadingCache<>(new AccountLoader(), 120000l);
this.secureRandom = new SecureRandom(); this.secureRandom = new SecureRandom();
} }
@ -473,7 +474,7 @@ public class DefaultAuthSystem {
Property prop = props[i]; Property prop = props[i];
if("textures".equals(prop.getName())) { if("textures".equals(prop.getName())) {
byte[] texturesData = Base64.decodeBase64(prop.getValue()); byte[] texturesData = Base64.decodeBase64(prop.getValue());
byte[] signatureData = prop.getSignature() == null ? new byte[0] : Base64.decodeBase64(prop.getSignature()); byte[] signatureData = prop.getSignature() == null ? ArrayUtils.EMPTY_BYTE_ARRAY : Base64.decodeBase64(prop.getSignature());
ByteArrayOutputStream bao = new ByteArrayOutputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dao = new DataOutputStream(bao); DataOutputStream dao = new DataOutputStream(bao);
dao.writeInt(texturesData.length); dao.writeInt(texturesData.length);

View File

@ -0,0 +1,93 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import io.netty.buffer.Unpooled;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.OverflowPacketException;
import net.md_5.bungee.protocol.packet.PluginMessage;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class CommandClientBrand extends Command {
public CommandClientBrand() {
super("client-brand", "eaglercraft.command.clientbrand", "clientbrand");
}
@Override
public void execute(CommandSender var1, String[] var2) {
if(var2.length == 1) {
ProxiedPlayer player = BungeeCord.getInstance().getPlayer(var2[0]);
if(player != null) {
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler)player.getPendingConnection();
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Brand: " + ChatColor.WHITE + handler.getEaglerBrandString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Version: " + ChatColor.WHITE + handler.getEaglerVersionString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client UUID: " + ChatColor.WHITE + handler.getClientBrandUUID()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Minecraft Client Brand: " + ChatColor.WHITE + decodeMCBrand(handler.getBrandMessage())));
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player is not using eaglercraft!"));
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player was not found!"));
}
return;
}
if(var2.length == 2) {
ProxiedPlayer player = BungeeCord.getInstance().getPlayer(var2[1]);
if(player != null) {
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler)player.getPendingConnection();
if("uuid".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client UUID: " + ChatColor.WHITE + handler.getClientBrandUUID()));
return;
}else if("name".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Brand: " + ChatColor.WHITE + handler.getEaglerBrandString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Version: " + ChatColor.WHITE + handler.getEaglerVersionString()));
return;
}else if("mc".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Minecraft Client Brand: " + ChatColor.WHITE + decodeMCBrand(handler.getBrandMessage())));
return;
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player is not using eaglercraft!"));
return;
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player was not found!"));
return;
}
}
var1.sendMessage(new TextComponent(ChatColor.RED + "Usage: /client-brand [uuid|name|mc] <username>"));
}
private static String decodeMCBrand(PluginMessage pkt) {
if(pkt == null) {
return "null";
}
try {
return DefinedPacket.readString(Unpooled.wrappedBuffer(pkt.getData()), 64);
}catch(OverflowPacketException | IndexOutOfBoundsException ex) {
return "null";
}
}
}

View File

@ -45,7 +45,7 @@ public class CommandDomain extends Command {
var1.sendMessage(new TextComponent(ChatColor.RED + "That user is not using Eaglercraft")); var1.sendMessage(new TextComponent(ChatColor.RED + "That user is not using Eaglercraft"));
return; return;
} }
String origin = ((EaglerInitialHandler)conn).origin; String origin = ((EaglerInitialHandler)conn).getOrigin();
if(origin != null) { if(origin != null) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Domain of " + var2[0] + " is '" + origin + "'")); var1.sendMessage(new TextComponent(ChatColor.BLUE + "Domain of " + var2[0] + " is '" + origin + "'"));
}else { }else {

View File

@ -3,10 +3,12 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileWriter; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -14,6 +16,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
@ -26,6 +29,7 @@ import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpContentType; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpContentType;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.ConfigurationProvider;
@ -50,7 +54,7 @@ import net.md_5.bungee.protocol.Property;
public class EaglerBungeeConfig { public class EaglerBungeeConfig {
public static EaglerBungeeConfig loadConfig(File directory) throws IOException { public static EaglerBungeeConfig loadConfig(File directory) throws IOException {
Map<String, HttpContentType> contentTypes = new HashMap(); Map<String, HttpContentType> contentTypes = new HashMap<>();
try(InputStream is = new FileInputStream(getConfigFile(directory, "http_mime_types.json"))) { try(InputStream is = new FileInputStream(getConfigFile(directory, "http_mime_types.json"))) {
loadMimeTypes(is, contentTypes); loadMimeTypes(is, contentTypes);
@ -68,6 +72,7 @@ public class EaglerBungeeConfig {
Configuration configYml = prov.load(getConfigFile(directory, "settings.yml")); Configuration configYml = prov.load(getConfigFile(directory, "settings.yml"));
String serverName = configYml.getString("server_name", "EaglercraftXBungee Server"); String serverName = configYml.getString("server_name", "EaglercraftXBungee Server");
EaglerXBungeeAPIHelper.getTemplateGlobals().put("server_name", serverName);
String serverUUIDString = configYml.getString("server_uuid", null); String serverUUIDString = configYml.getString("server_uuid", null);
if(serverUUIDString == null) { if(serverUUIDString == null) {
throw new IOException("You must specify a server_uuid!"); throw new IOException("You must specify a server_uuid!");
@ -85,7 +90,7 @@ public class EaglerBungeeConfig {
Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml")); Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml"));
Iterator<String> listeners = listenerYml.getKeys().iterator(); Iterator<String> listeners = listenerYml.getKeys().iterator();
Map<String, EaglerListenerConfig> serverListeners = new HashMap(); Map<String, EaglerListenerConfig> serverListeners = new HashMap<>();
boolean voiceChat = false; boolean voiceChat = false;
while(listeners.hasNext()) { while(listeners.hasNext()) {
@ -119,6 +124,43 @@ public class EaglerBungeeConfig {
} }
} }
File pauseMenuFolder = new File(directory, "pause_menu");
if(!pauseMenuFolder.isDirectory() && !pauseMenuFolder.mkdir()) {
throw new IOException("Could not create directory: " + pauseMenuFolder.getAbsolutePath());
}
File pauseMenuYml = new File(pauseMenuFolder, "pause_menu.yml");
if(!pauseMenuYml.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu.yml")) {
copyConfigFile(is, pauseMenuYml);
}
File f2 = new File(pauseMenuFolder, "server_info.html");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu_server_info.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "test_image.png");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu_test_image.png")) {
copyBinaryFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "message_api_example.html");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_message_api_example.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "message_api_v1.js");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_message_api_v1.js")) {
copyConfigFile(is, f2);
}
}
}
EaglerPauseMenuConfig pauseMenuConfig = EaglerPauseMenuConfig.loadConfig(prov.load(pauseMenuYml), pauseMenuFolder);
long websocketKeepAliveTimeout = configYml.getInt("websocket_connection_timeout", 15000); long websocketKeepAliveTimeout = configYml.getInt("websocket_connection_timeout", 15000);
long websocketHandshakeTimeout = configYml.getInt("websocket_handshake_timeout", 5000); long websocketHandshakeTimeout = configYml.getInt("websocket_handshake_timeout", 5000);
long builtinHttpServerTimeout = configYml.getInt("builtin_http_server_timeout", 10000); long builtinHttpServerTimeout = configYml.getInt("builtin_http_server_timeout", 10000);
@ -144,9 +186,10 @@ public class EaglerBungeeConfig {
eaglerPlayersVanillaSkin = null; eaglerPlayersVanillaSkin = null;
} }
boolean enableIsEaglerPlayerProperty = configYml.getBoolean("enable_is_eagler_player_property", true); boolean enableIsEaglerPlayerProperty = configYml.getBoolean("enable_is_eagler_player_property", true);
Set<String> disableVoiceOnServers = new HashSet((Collection<String>)configYml.getList("disable_voice_chat_on_servers")); Set<String> disableVoiceOnServers = new HashSet<>((Collection<String>)configYml.getList("disable_voice_chat_on_servers"));
boolean disableFNAWSkinsEverywhere = configYml.getBoolean("disable_fnaw_skins_everywhere", false); boolean disableFNAWSkinsEverywhere = configYml.getBoolean("disable_fnaw_skins_everywhere", false);
Set<String> disableFNAWSkinsOnServers = new HashSet((Collection<String>)configYml.getList("disable_fnaw_skins_on_servers")); Set<String> disableFNAWSkinsOnServers = new HashSet<>((Collection<String>)configYml.getList("disable_fnaw_skins_on_servers"));
boolean enableBackendRPCAPI = configYml.getBoolean("enable_backend_rpc_api", false);
final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout, final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout,
websocketHandshakeTimeout, builtinHttpServerTimeout, websocketCompressionLevel, serverListeners, websocketHandshakeTimeout, builtinHttpServerTimeout, websocketCompressionLevel, serverListeners,
@ -154,7 +197,7 @@ public class EaglerBungeeConfig {
skinRateLimitPlayer, skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects, skinRateLimitPlayer, skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects,
maxProfiles, antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin, maxProfiles, antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin,
enableIsEaglerPlayerProperty, authConfig, updatesConfig, iceServers, voiceChat, disableVoiceOnServers, enableIsEaglerPlayerProperty, authConfig, updatesConfig, iceServers, voiceChat, disableVoiceOnServers,
disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers); disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers, enableBackendRPCAPI, pauseMenuConfig);
if(eaglerPlayersVanillaSkin != null) { if(eaglerPlayersVanillaSkin != null) {
VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret); VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret);
@ -168,7 +211,7 @@ public class EaglerBungeeConfig {
if(!file.isFile()) { if(!file.isFile()) {
try (BufferedReader is = new BufferedReader(new InputStreamReader( try (BufferedReader is = new BufferedReader(new InputStreamReader(
EaglerBungeeConfig.class.getResourceAsStream("default_" + fileName), StandardCharsets.UTF_8)); EaglerBungeeConfig.class.getResourceAsStream("default_" + fileName), StandardCharsets.UTF_8));
PrintWriter os = new PrintWriter(new FileWriter(file))) { PrintWriter os = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
String line; String line;
while((line = is.readLine()) != null) { while((line = is.readLine()) != null) {
if(line.contains("${")) { if(line.contains("${")) {
@ -181,6 +224,26 @@ public class EaglerBungeeConfig {
return file; return file;
} }
private static void copyConfigFile(InputStream is, File file) throws IOException {
try(PrintWriter os = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String line;
while((line = reader.readLine()) != null) {
os.println(line);
}
}
}
private static void copyBinaryFile(InputStream is, File file) throws IOException {
try(OutputStream os = new FileOutputStream(file)) {
byte[] copyBuffer = new byte[1024];
int i;
while((i = is.read(copyBuffer)) != -1) {
os.write(copyBuffer, 0, i);
}
}
}
private static void loadMimeTypes(InputStream file, Map<String, HttpContentType> contentTypes) throws IOException { private static void loadMimeTypes(InputStream file, Map<String, HttpContentType> contentTypes) throws IOException {
JsonObject obj = parseJsonObject(file); JsonObject obj = parseJsonObject(file);
for(Entry<String, JsonElement> etr : obj.entrySet()) { for(Entry<String, JsonElement> etr : obj.entrySet()) {
@ -192,7 +255,7 @@ public class EaglerBungeeConfig {
EaglerXBungee.logger().warning("MIME type '" + mime + "' defines no extensions!"); EaglerXBungee.logger().warning("MIME type '" + mime + "' defines no extensions!");
continue; continue;
} }
HashSet<String> exts = new HashSet(); HashSet<String> exts = new HashSet<>();
for(int i = 0, l = arr.size(); i < l; ++i) { for(int i = 0, l = arr.size(); i < l; ++i) {
exts.add(arr.get(i).getAsString()); exts.add(arr.get(i).getAsString());
} }
@ -217,8 +280,8 @@ public class EaglerBungeeConfig {
} }
private static Collection<String> loadICEServers(Configuration config) { private static Collection<String> loadICEServers(Configuration config) {
Collection<String> ret = new ArrayList(config.getList("voice_stun_servers")); Collection<String> ret = new ArrayList<>(config.contains("voice_stun_servers") ? (List<String>)config.getList("voice_stun_servers") : (List<String>)config.getList("voice_servers_no_passwd"));
Configuration turnServers = config.getSection("voice_turn_servers"); Configuration turnServers = config.contains("voice_turn_servers") ? config.getSection("voice_turn_servers") : config.getSection("voice_servers_passwd");
Iterator<String> turnItr = turnServers.getKeys().iterator(); Iterator<String> turnItr = turnServers.getKeys().iterator();
while(turnItr.hasNext()) { while(turnItr.hasNext()) {
String name = turnItr.next(); String name = turnItr.next();
@ -269,6 +332,8 @@ public class EaglerBungeeConfig {
private final Set<String> disableVoiceOnServers; private final Set<String> disableVoiceOnServers;
private final boolean disableFNAWSkinsEverywhere; private final boolean disableFNAWSkinsEverywhere;
private final Set<String> disableFNAWSkinsOnServers; private final Set<String> disableFNAWSkinsOnServers;
private final boolean enableBackendRPCAPI;
private final EaglerPauseMenuConfig pauseMenuConf;
private boolean isCrackedFlag; private boolean isCrackedFlag;
Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty }; Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty };
@ -435,6 +500,14 @@ public class EaglerBungeeConfig {
return disableFNAWSkinsOnServers; return disableFNAWSkinsOnServers;
} }
public boolean getEnableBackendRPCAPI() {
return enableBackendRPCAPI;
}
public EaglerPauseMenuConfig getPauseMenuConf() {
return pauseMenuConf;
}
private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout, private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, long builtinHttpServerTimeout, int httpWebsocketCompressionLevel, long websocketHandshakeTimeout, long builtinHttpServerTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes, Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes,
@ -444,7 +517,8 @@ public class EaglerBungeeConfig {
String sqliteDriverClass, String sqliteDriverPath, String eaglerPlayersVanillaSkin, String sqliteDriverClass, String sqliteDriverPath, String eaglerPlayersVanillaSkin,
boolean enableIsEaglerPlayerProperty, EaglerAuthConfig authConfig, EaglerUpdateConfig updateConfig, boolean enableIsEaglerPlayerProperty, EaglerAuthConfig authConfig, EaglerUpdateConfig updateConfig,
Collection<String> iceServers, boolean enableVoiceChat, Set<String> disableVoiceOnServers, Collection<String> iceServers, boolean enableVoiceChat, Set<String> disableVoiceOnServers,
boolean disableFNAWSkinsEverywhere, Set<String> disableFNAWSkinsOnServers) { boolean disableFNAWSkinsEverywhere, Set<String> disableFNAWSkinsOnServers,
boolean enableBackendRPCAPI, EaglerPauseMenuConfig pauseMenuConf) {
this.serverName = serverName; this.serverName = serverName;
this.serverUUID = serverUUID; this.serverUUID = serverUUID;
this.serverListeners = serverListeners; this.serverListeners = serverListeners;
@ -476,6 +550,8 @@ public class EaglerBungeeConfig {
this.disableVoiceOnServers = disableVoiceOnServers; this.disableVoiceOnServers = disableVoiceOnServers;
this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere; this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere;
this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers; this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers;
this.enableBackendRPCAPI = enableBackendRPCAPI;
this.pauseMenuConf = pauseMenuConf;
} }
} }

View File

@ -74,8 +74,14 @@ public class EaglerListenerConfig extends ListenerInfo {
for(int i = 0, l = serverMOTD.size(); i < l; ++i) { for(int i = 0, l = serverMOTD.size(); i < l; ++i) {
serverMOTD.set(i, ChatColor.translateAlternateColorCodes('&', serverMOTD.get(i))); serverMOTD.set(i, ChatColor.translateAlternateColorCodes('&', serverMOTD.get(i)));
} }
boolean allowMOTD = config.getBoolean("allow_motd", false); boolean allowMOTD = config.getBoolean("allow_motd", true);
boolean allowQuery = config.getBoolean("allow_query", false); boolean allowQuery = config.getBoolean("allow_query", true);
boolean allowV3 = config.getBoolean("allow_protocol_v3", true);
boolean allowV4 = config.getBoolean("allow_protocol_v4", true);
if(!allowV3 && !allowV4) {
throw new IllegalArgumentException("Both v3 and v4 protocol are disabled!");
}
int defragSendDelay = config.getInt("protocol_v4_defrag_send_delay", 10);
int cacheTTL = 7200; int cacheTTL = 7200;
boolean cacheAnimation = false; boolean cacheAnimation = false;
@ -102,8 +108,8 @@ public class EaglerListenerConfig extends ListenerInfo {
page404 = null; page404 = null;
} }
List<String> defaultIndex = Arrays.asList("index.html", "index.htm"); List<String> defaultIndex = Arrays.asList("index.html", "index.htm");
List indexPageRaw = httpServerConf.getList("page_index_name", defaultIndex); List<?> indexPageRaw = httpServerConf.getList("page_index_name", defaultIndex);
List<String> indexPage = new ArrayList(indexPageRaw.size()); List<String> indexPage = new ArrayList<>(indexPageRaw.size());
for(int i = 0, l = indexPageRaw.size(); i < l; ++i) { for(int i = 0, l = indexPageRaw.size(); i < l; ++i) {
Object o = indexPageRaw.get(i); Object o = indexPageRaw.get(i);
@ -151,7 +157,8 @@ public class EaglerListenerConfig extends ListenerInfo {
cacheTrending, cachePortfolios); cacheTrending, cachePortfolios);
return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer, return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer,
forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery, forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery,
cacheConfig, httpServer, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery); allowV3, allowV4, defragSendDelay, cacheConfig, httpServer, enableVoiceChat, ratelimitIp,
ratelimitLogin, ratelimitMOTD, ratelimitQuery);
} }
private final InetSocketAddress address; private final InetSocketAddress address;
@ -167,6 +174,9 @@ public class EaglerListenerConfig extends ListenerInfo {
private final List<String> serverMOTD; private final List<String> serverMOTD;
private final boolean allowMOTD; private final boolean allowMOTD;
private final boolean allowQuery; private final boolean allowQuery;
private final boolean allowV3;
private final boolean allowV4;
private final int defragSendDelay;
private final MOTDCacheConfiguration motdCacheConfig; private final MOTDCacheConfiguration motdCacheConfig;
private final HttpWebServer webServer; private final HttpWebServer webServer;
private boolean serverIconSet = false; private boolean serverIconSet = false;
@ -180,9 +190,10 @@ public class EaglerListenerConfig extends ListenerInfo {
public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer, public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer,
String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp, String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp,
String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD, String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD,
boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, boolean allowMOTD, boolean allowQuery, boolean allowV3, boolean allowV4, int defragSendDelay,
boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, boolean enableVoiceChat,
EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) { EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, EaglerRateLimiter ratelimitMOTD,
EaglerRateLimiter ratelimitQuery) {
super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer, super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer,
Collections.emptyMap(), tabListType, false, false, 0, false, false); Collections.emptyMap(), tabListType, false, false, 0, false, false);
this.address = address; this.address = address;
@ -198,6 +209,9 @@ public class EaglerListenerConfig extends ListenerInfo {
this.serverMOTD = serverMOTD; this.serverMOTD = serverMOTD;
this.allowMOTD = allowMOTD; this.allowMOTD = allowMOTD;
this.allowQuery = allowQuery; this.allowQuery = allowQuery;
this.allowV3 = allowV3;
this.allowV4 = allowV4;
this.defragSendDelay = defragSendDelay;
this.motdCacheConfig = motdCacheConfig; this.motdCacheConfig = motdCacheConfig;
this.webServer = webServer; this.webServer = webServer;
this.enableVoiceChat = enableVoiceChat; this.enableVoiceChat = enableVoiceChat;
@ -273,6 +287,18 @@ public class EaglerListenerConfig extends ListenerInfo {
return allowQuery; return allowQuery;
} }
public boolean isAllowV3() {
return allowV3;
}
public boolean isAllowV4() {
return allowV4;
}
public int getDefragSendDelay() {
return defragSendDelay;
}
public HttpWebServer getWebServer() { public HttpWebServer getWebServer() {
return webServer; return webServer;
} }

View File

@ -0,0 +1,251 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketServerInfoDataChunkV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
import net.md_5.bungee.config.Configuration;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerPauseMenuConfig {
private boolean enableCustomPauseMenu;
private SPacketCustomizePauseMenuV4EAG customPauseMenuPacket;
private byte[] serverInfoHash;
private List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks;
private int infoSendRate;
static EaglerPauseMenuConfig loadConfig(Configuration conf, File baseDir) throws IOException {
boolean enabled = conf.getBoolean("enable_custom_pause_menu", false);
if(!enabled) {
return new EaglerPauseMenuConfig(false, null, null, 1);
}
Configuration server_info_button = conf.getSection("server_info_button");
boolean enableInfoButton = server_info_button.getBoolean("enable_button", false);
String infoButtonText = server_info_button.getString("button_text", "Server Info");
boolean infoButtonModeNewTab = server_info_button.getBoolean("button_mode_open_new_tab", false);
String infoButtonEmbedURL = server_info_button.getString("server_info_embed_url", "");
boolean infoButtonModeEmbedFile = server_info_button.getBoolean("button_mode_embed_file", true);
String infoButtonEmbedFile = server_info_button.getString("server_info_embed_file", "server_info.html");
String infoButtonEmbedScreenTitle = server_info_button.getString("server_info_embed_screen_title", "Server Info");
int infoSendRate = server_info_button.getInt("server_info_embed_send_chunk_rate", 1);
int infoChunkSize = server_info_button.getInt("server_info_embed_send_chunk_size", 24576);
if(infoChunkSize > 32720) {
throw new IOException("Chunk size " +infoChunkSize + " is too large! Max is 32720 bytes");
}
boolean infoButtonEnableTemplateMacros = server_info_button.getBoolean("enable_template_macros", true);
Configuration globals = server_info_button.getSection("server_info_embed_template_globals");
for(String s : globals.getKeys()) {
EaglerXBungeeAPIHelper.getTemplateGlobals().put(s, globals.getString(s));
}
boolean infoButtonAllowTemplateEvalMacro = server_info_button.getBoolean("allow_embed_template_eval_macro", false);
boolean infoButtonEnableWebviewJavascript = server_info_button.getBoolean("enable_webview_javascript", false);
boolean infoButtonEnableWebviewMessageAPI = server_info_button.getBoolean("enable_webview_message_api", false);
boolean infoButtonEnableWebviewStrictCSP = server_info_button.getBoolean("enable_webview_strict_csp", true);
Configuration discord_button = conf.getSection("discord_button");
boolean enableDiscordButton = discord_button.getBoolean("enable_button", false);
String discordButtonText = discord_button.getString("button_text", "Discord");
String discordButtonURL = discord_button.getString("button_url", "https://invite url here");
int infoButtonMode = enableInfoButton
? (infoButtonModeEmbedFile
? (infoButtonEmbedFile.length() > 0
? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE)
: (infoButtonEmbedURL.length() > 0
? (infoButtonModeNewTab ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_EXTERNAL_URL
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_HTTP)
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE))
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE;
int discordButtonMode = (enableDiscordButton && discordButtonURL.length() > 0)
? SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_INVITE_URL
: SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_NONE;
int webviewPerms = (infoButtonEnableWebviewJavascript ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_JAVASCRIPT : 0) |
(infoButtonEnableWebviewMessageAPI ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_MESSAGE_API : 0) |
(infoButtonEnableWebviewStrictCSP ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_STRICT_CSP : 0);
Map<String,String> imagesToActuallyLoad = new WeakHashMap<>();
Configuration custom_images = conf.getSection("custom_images");
for(String s : custom_images.getKeys()) {
String fileName = custom_images.getString(s, "");
if(fileName.length() > 0) {
imagesToActuallyLoad.put(s, fileName);
}
}
Map<String,Integer> imageMappings = null;
List<PacketImageData> customImageDatas = null;
if(imagesToActuallyLoad.size() > 0) {
Map<String,PacketImageData> imageLoadingCache = new HashMap<>();
TIntObjectMap<PacketImageData> imageDumbHashTable = new TIntObjectHashMap<>();
imageMappings = new HashMap<>();
customImageDatas = new ArrayList<>();
outer_loop: for(Entry<String,String> etr : imagesToActuallyLoad.entrySet()) {
String key = etr.getKey();
String value = etr.getValue();
PacketImageData existing = imageLoadingCache.get(value);
if(existing != null) {
for(int i = 0, l = customImageDatas.size(); i < l; ++i) {
if(customImageDatas.get(i) == existing) {
imageMappings.put(key, i);
continue outer_loop;
}
}
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(existing);
continue outer_loop;
}else {
PacketImageData img = EaglerXBungeeAPIHelper.loadPacketImageData(new File(baseDir, value), 64, 64);
int hashCode = Arrays.hashCode(img.rgba);
PacketImageData possibleClone = imageDumbHashTable.get(hashCode);
if (possibleClone != null && possibleClone.width == img.width && possibleClone.height == img.height
&& Arrays.equals(img.rgba, possibleClone.rgba)) {
for(int i = 0, l = customImageDatas.size(); i < l; ++i) {
if(customImageDatas.get(i) == possibleClone) {
imageMappings.put(key, i);
continue outer_loop;
}
}
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(possibleClone);
continue outer_loop;
}else {
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(img);
imageDumbHashTable.put(hashCode, img);
continue outer_loop;
}
}
}
}
SPacketCustomizePauseMenuV4EAG pausePacket = new SPacketCustomizePauseMenuV4EAG();
List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks = null;
pausePacket.serverInfoMode = infoButtonMode;
switch(infoButtonMode) {
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE:
default:
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_EXTERNAL_URL:
pausePacket.serverInfoButtonText = infoButtonText;
pausePacket.serverInfoURL = infoButtonEmbedURL;
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_HTTP:
pausePacket.serverInfoButtonText = infoButtonText;
pausePacket.serverInfoURL = infoButtonEmbedURL;
pausePacket.serverInfoEmbedPerms = webviewPerms;
pausePacket.serverInfoEmbedTitle = infoButtonEmbedScreenTitle;
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS:
pausePacket.serverInfoButtonText = infoButtonText;
byte[] hash = new byte[20];
String rawData = EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(baseDir, infoButtonEmbedFile));
if(infoButtonEnableTemplateMacros) {
rawData = EaglerXBungeeAPIHelper.loadServerInfoTemplateEagler(rawData, baseDir, infoButtonAllowTemplateEvalMacro);
}
serverInfoChunks = EaglerXBungeeAPIHelper.convertServerInfoToChunks(rawData.getBytes(StandardCharsets.UTF_8), hash, infoChunkSize);
if(!serverInfoChunks.isEmpty()) {
SPacketServerInfoDataChunkV4EAG pk = serverInfoChunks.get(0);
EaglerXBungee.logger().info("Total server info embed size: " + pk.finalSize + " bytes" + (serverInfoChunks.size() > 1 ? (" (" + serverInfoChunks.size() + " chunks)") : ""));
}
pausePacket.serverInfoEmbedPerms = webviewPerms;
pausePacket.serverInfoEmbedTitle = infoButtonEmbedScreenTitle;
pausePacket.serverInfoHash = hash;
break;
}
pausePacket.discordButtonMode = discordButtonMode;
switch(discordButtonMode) {
case SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_NONE:
default:
break;
case SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_INVITE_URL:
pausePacket.discordButtonMode = discordButtonMode;
pausePacket.discordButtonText = discordButtonText;
pausePacket.discordInviteURL = discordButtonURL;
break;
}
pausePacket.imageMappings = imageMappings;
pausePacket.imageData = customImageDatas;
SimpleOutputBufferImpl ob = new SimpleOutputBufferImpl(new TestOutputStream());
pausePacket.writePacket(ob);
int cnt = ob.size();
EaglerXBungee.logger().info("Total pause menu packet size: " + cnt + " bytes");
if(cnt > 32760) {
throw new IOException("Pause menu packet is " + (cnt - 32760) + " bytes too large! Try making the images smaller or reusing the same image file for multiple icons!");
}
return new EaglerPauseMenuConfig(enabled, pausePacket, serverInfoChunks, infoSendRate);
}
private EaglerPauseMenuConfig(boolean enableCustomPauseMenu, SPacketCustomizePauseMenuV4EAG customPauseMenuPacket,
List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks, int infoSendRate) {
this.enableCustomPauseMenu = enableCustomPauseMenu;
this.customPauseMenuPacket = customPauseMenuPacket;
this.serverInfoHash = customPauseMenuPacket != null ? customPauseMenuPacket.serverInfoHash : null;
this.serverInfoChunks = serverInfoChunks;
this.infoSendRate = infoSendRate;
}
public boolean getEnabled() {
return enableCustomPauseMenu;
}
public SPacketCustomizePauseMenuV4EAG getPacket() {
return customPauseMenuPacket;
}
public byte[] getServerInfoHash() {
return serverInfoHash;
}
public List<SPacketServerInfoDataChunkV4EAG> getServerInfo() {
return serverInfoChunks;
}
public int getInfoSendRate() {
return infoSendRate;
}
}

View File

@ -7,6 +7,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
/** /**
@ -95,7 +96,7 @@ public class EaglerRateLimiter {
protected long cooldownTimestamp = 0l; protected long cooldownTimestamp = 0l;
protected RateLimitStatus rateLimit() { protected RateLimitStatus rateLimit() {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
tick(millis); tick(millis);
if(lockoutTimestamp != 0l) { if(lockoutTimestamp != 0l) {
return RateLimitStatus.LOCKED_OUT; return RateLimitStatus.LOCKED_OUT;
@ -136,7 +137,7 @@ public class EaglerRateLimiter {
} }
} }
private final Map<String, RateLimiter> ratelimiters = new HashMap(); private final Map<String, RateLimiter> ratelimiters = new HashMap<>();
public RateLimitStatus rateLimit(String addr) { public RateLimitStatus rateLimit(String addr) {
addr = addr.toLowerCase(); addr = addr.toLowerCase();
@ -156,7 +157,7 @@ public class EaglerRateLimiter {
} }
public void tick() { public void tick() {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
synchronized(ratelimiters) { synchronized(ratelimiters) {
Iterator<RateLimiter> itr = ratelimiters.values().iterator(); Iterator<RateLimiter> itr = ratelimiters.values().iterator();
while(itr.hasNext()) { while(itr.hasNext()) {
@ -181,7 +182,7 @@ public class EaglerRateLimiter {
int limitLockout = config.getInt("limit_lockout", -1); int limitLockout = config.getInt("limit_lockout", -1);
int lockoutDuration = config.getInt("lockout_duration", -1); int lockoutDuration = config.getInt("lockout_duration", -1);
Collection<String> exc = (Collection<String>) config.getList("exceptions"); Collection<String> exc = (Collection<String>) config.getList("exceptions");
List<String> exceptions = new ArrayList(); List<String> exceptions = new ArrayList<>();
for(String str : exc) { for(String str : exc) {
exceptions.add(str.toLowerCase()); exceptions.add(str.toLowerCase());
} }

View File

@ -0,0 +1,250 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import com.google.common.html.HtmlEscapers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.repackage.lang3.StrTokenizer;
import net.md_5.bungee.BungeeCord;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ServerInfoTemplateParser {
private static final Gson jsonEscaper = (new GsonBuilder()).disableHtmlEscaping().create();
private static class State {
private boolean evalAllowed;
private File baseDir;
private Map<String, String> globals;
private boolean htmlEscape;
private boolean strEscape;
private boolean disableMacros;
private boolean enableEval;
private State(File baseDir, boolean evalAllowed, Map<String, String> globals) {
this.baseDir = baseDir;
this.evalAllowed = evalAllowed;
this.globals = globals;
}
private State push() {
return new State(baseDir, evalAllowed, globals);
}
}
public static String loadTemplate(String content, File baseDir, boolean evalAllowed, Map<String, String> globals) throws IOException {
return loadTemplate(content, new State(baseDir, evalAllowed, globals));
}
private static String loadTemplate(String content, State state) throws IOException {
StringBuilder ret = new StringBuilder();
int i = 0, j = 0;
while((i = content.indexOf("{%", j)) != -1) {
ret.append(content, j, i);
j = i;
i = content.indexOf("%}", j + 2);
if(i != -1) {
ret.append(processMacro(content.substring(j + 2, i), state));
j = i + 2;
}else {
break;
}
}
ret.append(content, j, content.length());
return ret.toString();
}
public static class InvalidMacroException extends RuntimeException {
public InvalidMacroException(String message, Throwable cause) {
super(message, cause);
}
public InvalidMacroException(String message) {
super(message);
}
}
private static String processMacro(String content, State state) throws IOException {
String trimmed = content.trim();
try {
String[] strs = (new StrTokenizer(trimmed, ' ', '`')).getTokenArray();
if(strs.length < 1) {
return "{%" + content + "%}";
}
if(strs[0].equals("disablemacros") && strs.length == 2) {
switch(strs[1]) {
case "on":
if(state.disableMacros) {
return "{%" + content + "%}";
}else {
state.disableMacros = true;
return "";
}
case "off":
state.disableMacros = false;
return "";
default:
if(state.disableMacros) {
return "{%" + content + "%}";
}else {
throw new InvalidMacroException("Unknown disablemacros mode: " + strs[1] + " (Expected: on, off)");
}
}
}else if(!state.disableMacros) {
switch(strs[0]) {
case "embed":
argCheck(3, strs.length);
switch(strs[1]) {
case "base64":
return Base64.encodeBase64String(EaglerXBungeeAPIHelper.loadFileToByteArrayServerInfo(new File(state.baseDir, strs[2])));
case "text":
return escapeMacroResult(EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(state.baseDir, strs[2])), state);
case "eval":
if(state.evalAllowed) {
return escapeMacroResult(loadTemplate(EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(state.baseDir, strs[2])), state.push()), state);
}else {
throw new InvalidMacroException("Template tried to eval file \"" + strs[2] + "\"! (eval is disabled)");
}
default:
throw new InvalidMacroException("Unknown embed mode: " + strs[1] + " (Expected: base64, text, eval)");
}
case "htmlescape":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
state.htmlEscape = true;
return "";
case "off":
state.htmlEscape = false;
return "";
default:
throw new InvalidMacroException("Unknown htmlescape mode: " + strs[1] + " (Expected: on, off)");
}
case "strescape":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
state.strEscape = true;
return "";
case "off":
state.strEscape = false;
return "";
default:
throw new InvalidMacroException("Unknown strescape mode: " + strs[1] + " (Expected: on, off)");
}
case "eval":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
if(!state.evalAllowed) {
throw new InvalidMacroException("Template tried to enable eval! (eval is disabled)");
}
state.enableEval = true;
return "";
case "off":
state.enableEval = false;
return "";
default:
throw new InvalidMacroException("Unknown eval mode: " + strs[1] + " (Expected: on, off)");
}
case "global":
argCheck(2, 3, strs.length);
String ret = state.globals.get(strs[1]);
if(ret == null) {
if(strs.length == 3) {
ret = strs[2];
}else {
throw new InvalidMacroException("Unknown global \"" + strs[1] + "\"! (Available: " + String.join(", ", state.globals.keySet()) + ")");
}
}
return escapeMacroResult(ret, state);
case "property":
argCheck(2, 3, strs.length);
ret = System.getProperty(strs[1]);
if(ret == null) {
if(strs.length == 3) {
ret = strs[2];
}else {
throw new InvalidMacroException("Unknown system property \"" + strs[1] + "\"!");
}
}
return escapeMacroResult(ret, state);
case "text":
argCheck(2, strs.length);
return escapeMacroResult(strs[1], state);
case "translate":
argCheckMin(2, strs.length);
String[] additionalArgs = new String[strs.length - 2];
System.arraycopy(strs, 2, additionalArgs, 0, additionalArgs.length);
return escapeMacroResult(BungeeCord.getInstance().getTranslation(strs[1], (Object[])additionalArgs), state);
default:
return "{%" + content + "%}";
}
}else {
return "{%" + content + "%}";
}
}catch(InvalidMacroException ex) {
throw new IOException("Invalid macro: {% " + trimmed + " %}, message: " + ex.getMessage(), ex);
}catch(Throwable th) {
throw new IOException("Error processing: {% " + trimmed + " %}, raised: " + th.toString(), th);
}
}
private static String escapeMacroResult(String str, State state) throws IOException {
if(str.length() > 0) {
if(state.evalAllowed && state.enableEval) {
str = loadTemplate(str, state.push());
}
if(state.strEscape) {
str = jsonEscaper.toJson(str);
if(str.length() >= 2) {
str = str.substring(1, str.length() - 1);
}
}
if(state.htmlEscape) {
str = HtmlEscapers.htmlEscaper().escape(str);
}
}
return str;
}
private static void argCheck(int expect, int actual) {
if(expect != actual) {
throw new InvalidMacroException("Wrong number of arguments (" + actual + ", expected " + expect + ")");
}
}
private static void argCheck(int expectMin, int expectMax, int actual) {
if(expectMin > actual || expectMax < actual) {
throw new InvalidMacroException("Wrong number of arguments (" + actual + ", expected " + expectMin + " to " + expectMax + ")");
}
}
private static void argCheckMin(int expectMin, int actual) {
if(expectMin > actual) {
throw new InvalidMacroException("Wrong number of arguments (expected " + expectMin + " or more, got " + actual + ")");
}
}
}

View File

@ -1,9 +1,10 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import net.md_5.bungee.protocol.DefinedPacket; import java.io.IOException;
import java.io.OutputStream;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2024 lax1dude. All Rights Reserved.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -17,16 +18,14 @@ import net.md_5.bungee.protocol.DefinedPacket;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public class EaglerProtocolAccessProxy { public class TestOutputStream extends OutputStream {
public static int getPacketId(EaglerBungeeProtocol protocol, int protocolVersion, DefinedPacket pkt, boolean server) { @Override
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER; public void write(int b) throws IOException {
return prot.getId((Class) pkt.getClass(), protocolVersion);
} }
public static DefinedPacket createPacket(EaglerBungeeProtocol protocol, int protocolVersion, int packetId, boolean server) { @Override
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER; public void write(byte[] b, int o, int l) throws IOException {
return prot.createPacket(packetId, protocolVersion);
} }
} }

View File

@ -1,6 +1,5 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
@ -11,20 +10,18 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapeServiceOffline; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceSignalPackets;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
@ -39,7 +36,7 @@ import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.protocol.Property; import net.md_5.bungee.protocol.Property;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -55,7 +52,6 @@ import net.md_5.bungee.protocol.Property;
*/ */
public class EaglerPacketEventListener implements Listener { public class EaglerPacketEventListener implements Listener {
public static final String FNAW_SKIN_ENABLE_CHANNEL = "EAG|FNAWSEn-1.8";
public static final String GET_DOMAIN_CHANNEL = "EAG|GetDomain"; public static final String GET_DOMAIN_CHANNEL = "EAG|GetDomain";
public final EaglerXBungee plugin; public final EaglerXBungee plugin;
@ -68,50 +64,64 @@ public class EaglerPacketEventListener implements Listener {
public void onPluginMessage(final PluginMessageEvent event) { public void onPluginMessage(final PluginMessageEvent event) {
if(event.getSender() instanceof UserConnection) { if(event.getSender() instanceof UserConnection) {
final UserConnection player = (UserConnection)event.getSender(); final UserConnection player = (UserConnection)event.getSender();
String tag = event.getTag();
if(player.getPendingConnection() instanceof EaglerInitialHandler) { if(player.getPendingConnection() instanceof EaglerInitialHandler) {
if(SkinService.CHANNEL.equals(event.getTag())) { EaglerInitialHandler initialHandler = (EaglerInitialHandler)player.getPendingConnection();
event.setCancelled(true); GameProtocolMessageController msgController = initialHandler.getEaglerMessageController();
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() { if(msgController != null) {
@Override
public void run() {
try { try {
SkinPackets.processPacket(event.getData(), player, plugin.getSkinService()); if(msgController.handlePacket(tag, event.getData())) {
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Skin packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling skins!", e);
}
}
});
}else if(CapeServiceOffline.CHANNEL.equals(event.getTag())) {
event.setCancelled(true); event.setCancelled(true);
try { return;
CapePackets.processPacket(event.getData(), player, plugin.getCapeService());
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Cape packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling capes!", e);
} }
}else if(VoiceService.CHANNEL.equals(event.getTag())) { } catch (Throwable e) {
event.getSender().disconnect(new TextComponent("Eaglercraft packet error!"));
event.setCancelled(true); event.setCancelled(true);
VoiceService svc = plugin.getVoiceService(); return;
if(svc != null && ((EaglerInitialHandler)player.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
try {
VoiceSignalPackets.processPacket(event.getData(), player, svc);
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Voice signal packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling voice signals!", e);
} }
} }
} }
if(tag.equals(EaglerBackendRPCProtocol.CHANNEL_NAME)) {
event.getSender().disconnect(new TextComponent("Nope!"));
event.setCancelled(true);
return;
}
if(tag.equals(EaglerBackendRPCProtocol.CHANNEL_NAME_READY)) {
event.setCancelled(true);
return;
} }
}else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) { }else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getReceiver(); UserConnection player = (UserConnection)event.getReceiver();
if(GET_DOMAIN_CHANNEL.equals(event.getTag()) && player.getPendingConnection() instanceof EaglerInitialHandler) { String tag = event.getTag();
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler initialHandler = (EaglerInitialHandler)player.getPendingConnection();
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setCancelled(true); event.setCancelled(true);
String domain = ((EaglerInitialHandler)player.getPendingConnection()).getOrigin(); try {
if(domain == null) { initialHandler.handleBackendRPCPacket((Server)event.getSender(), event.getData());
((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 }); }catch(Throwable t) {
}else { EaglerXBungee.logger().log(Level.SEVERE, "[" + ((UserConnection) event.getReceiver()).getName()
+ "]: Caught an exception handling backend RPC packet!", t);
}
}else if(GET_DOMAIN_CHANNEL.equals(tag)) {
event.setCancelled(true);
String domain = initialHandler.getOrigin();
if(domain != null) {
((Server)event.getSender()).sendData("EAG|Domain", domain.getBytes(StandardCharsets.UTF_8)); ((Server)event.getSender()).sendData("EAG|Domain", domain.getBytes(StandardCharsets.UTF_8));
}else {
((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 });
}
}
}else {
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setCancelled(true);
try {
BackendRPCSessionHandler.handlePacketOnVanilla((Server) event.getSender(),
(UserConnection) event.getReceiver(), event.getData());
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + ((UserConnection) event.getReceiver()).getName()
+ "]: Caught an exception handling backend RPC packet!", t);
}
} }
} }
} }
@ -175,19 +185,7 @@ public class EaglerPacketEventListener implements Listener {
@EventHandler @EventHandler
public void onServerConnected(ServerConnectedEvent event) { public void onServerConnected(ServerConnectedEvent event) {
if(event.getPlayer() instanceof UserConnection) { if(event.getPlayer() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getPlayer(); EaglerPipeline.addServerConnectListener((UserConnection)event.getPlayer());
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler) player.getPendingConnection();
ServerInfo sv = event.getServer().getInfo();
boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere() && !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName());
if(fnawSkins != handler.currentFNAWSkinEnableStatus) {
handler.currentFNAWSkinEnableStatus = fnawSkins;
player.sendData(FNAW_SKIN_ENABLE_CHANNEL, new byte[] { fnawSkins ? (byte)1 : (byte)0 });
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(player, sv);
}
}
} }
} }
@ -195,11 +193,16 @@ public class EaglerPacketEventListener implements Listener {
public void onServerDisconnected(ServerDisconnectEvent event) { public void onServerDisconnected(ServerDisconnectEvent event) {
if(event.getPlayer() instanceof UserConnection) { if(event.getPlayer() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getPlayer(); UserConnection player = (UserConnection)event.getPlayer();
if((player.getPendingConnection() instanceof EaglerInitialHandler) if(player.getPendingConnection() instanceof EaglerInitialHandler) {
&& ((EaglerInitialHandler) player.getPendingConnection()).getEaglerListenerConfig() EaglerInitialHandler handler = (EaglerInitialHandler) player.getPendingConnection();
.getEnableVoiceChat()) { BackendRPCSessionHandler rpcHandler = handler.getRPCSessionHandler();
if(rpcHandler != null) {
rpcHandler.handleConnectionLost(event.getTarget());
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerDisconnected(player, event.getTarget()); plugin.getVoiceService().handleServerDisconnected(player, event.getTarget());
} }
} }
} }
} }
}

View File

@ -0,0 +1,439 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.repackage.lang3;
import java.util.Arrays;
import org.apache.commons.lang3.ArraySorter;
import org.apache.commons.lang3.StringUtils;
/**
* A matcher class that can be queried to determine if a character array portion
* matches.
* <p>
* This class comes complete with various factory methods. If these do not
* suffice, you can subclass and implement your own matcher.
*
* @since 2.2
* @!deprecated as of 3.6, use commons-text <a href=
* "https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/matcher/StringMatcherFactory.html">
* StringMatcherFactory</a> instead
*/
//@Deprecated
public abstract class StrMatcher {
/**
* Matches the comma character.
*/
private static final StrMatcher COMMA_MATCHER = new CharMatcher(',');
/**
* Matches the tab character.
*/
private static final StrMatcher TAB_MATCHER = new CharMatcher('\t');
/**
* Matches the space character.
*/
private static final StrMatcher SPACE_MATCHER = new CharMatcher(' ');
/**
* Matches the same characters as StringTokenizer, namely space, tab, newline,
* formfeed.
*/
private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray());
/**
* Matches the String trim() whitespace characters.
*/
private static final StrMatcher TRIM_MATCHER = new TrimMatcher();
/**
* Matches the double quote character.
*/
private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\'');
/**
* Matches the double quote character.
*/
private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"');
/**
* Matches the single or double quote character.
*/
private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray());
/**
* Matches no characters.
*/
private static final StrMatcher NONE_MATCHER = new NoMatcher();
// -----------------------------------------------------------------------
/**
* Returns a matcher which matches the comma character.
*
* @return a matcher for a comma
*/
public static StrMatcher commaMatcher() {
return COMMA_MATCHER;
}
/**
* Returns a matcher which matches the tab character.
*
* @return a matcher for a tab
*/
public static StrMatcher tabMatcher() {
return TAB_MATCHER;
}
/**
* Returns a matcher which matches the space character.
*
* @return a matcher for a space
*/
public static StrMatcher spaceMatcher() {
return SPACE_MATCHER;
}
/**
* Matches the same characters as StringTokenizer, namely space, tab, newline
* and formfeed.
*
* @return the split matcher
*/
public static StrMatcher splitMatcher() {
return SPLIT_MATCHER;
}
/**
* Matches the String trim() whitespace characters.
*
* @return the trim matcher
*/
public static StrMatcher trimMatcher() {
return TRIM_MATCHER;
}
/**
* Returns a matcher which matches the single quote character.
*
* @return a matcher for a single quote
*/
public static StrMatcher singleQuoteMatcher() {
return SINGLE_QUOTE_MATCHER;
}
/**
* Returns a matcher which matches the double quote character.
*
* @return a matcher for a double quote
*/
public static StrMatcher doubleQuoteMatcher() {
return DOUBLE_QUOTE_MATCHER;
}
/**
* Returns a matcher which matches the single or double quote character.
*
* @return a matcher for a single or double quote
*/
public static StrMatcher quoteMatcher() {
return QUOTE_MATCHER;
}
/**
* Matches no characters.
*
* @return a matcher that matches nothing
*/
public static StrMatcher noneMatcher() {
return NONE_MATCHER;
}
/**
* Constructor that creates a matcher from a character.
*
* @param ch the character to match, must not be null
* @return a new Matcher for the given char
*/
public static StrMatcher charMatcher(final char ch) {
return new CharMatcher(ch);
}
/**
* Constructor that creates a matcher from a set of characters.
*
* @param chars the characters to match, null or empty matches nothing
* @return a new matcher for the given char[]
*/
public static StrMatcher charSetMatcher(final char... chars) {
if (chars == null || chars.length == 0) {
return NONE_MATCHER;
}
if (chars.length == 1) {
return new CharMatcher(chars[0]);
}
return new CharSetMatcher(chars);
}
/**
* Constructor that creates a matcher from a string representing a set of
* characters.
*
* @param chars the characters to match, null or empty matches nothing
* @return a new Matcher for the given characters
*/
public static StrMatcher charSetMatcher(final String chars) {
if (StringUtils.isEmpty(chars)) {
return NONE_MATCHER;
}
if (chars.length() == 1) {
return new CharMatcher(chars.charAt(0));
}
return new CharSetMatcher(chars.toCharArray());
}
/**
* Constructor that creates a matcher from a string.
*
* @param str the string to match, null or empty matches nothing
* @return a new Matcher for the given String
*/
public static StrMatcher stringMatcher(final String str) {
if (StringUtils.isEmpty(str)) {
return NONE_MATCHER;
}
return new StringMatcher(str);
}
// -----------------------------------------------------------------------
/**
* Constructor.
*/
protected StrMatcher() {
}
/**
* Returns the number of matching characters, zero for no match.
* <p>
* This method is called to check for a match. The parameter {@code pos}
* represents the current position to be checked in the string {@code buffer} (a
* character array which must not be changed). The API guarantees that
* {@code pos} is a valid index for {@code buffer}.
* <p>
* The character array may be larger than the active area to be matched. Only
* values in the buffer between the specified indices may be accessed.
* <p>
* The matching code may check one character or many. It may check characters
* preceding {@code pos} as well as those after, so long as no checks exceed the
* bounds specified.
* <p>
* It must return zero for no match, or a positive number if a match was found.
* The number indicates the number of characters that matched.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index (exclusive) of the active buffer, valid for
* buffer
* @return the number of matching characters, zero for no match
*/
public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
/**
* Returns the number of matching characters, zero for no match.
* <p>
* This method is called to check for a match. The parameter {@code pos}
* represents the current position to be checked in the string {@code buffer} (a
* character array which must not be changed). The API guarantees that
* {@code pos} is a valid index for {@code buffer}.
* <p>
* The matching code may check one character or many. It may check characters
* preceding {@code pos} as well as those after.
* <p>
* It must return zero for no match, or a positive number if a match was found.
* The number indicates the number of characters that matched.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @return the number of matching characters, zero for no match
* @since 2.4
*/
public int isMatch(final char[] buffer, final int pos) {
return isMatch(buffer, pos, 0, buffer.length);
}
// -----------------------------------------------------------------------
/**
* Class used to define a set of characters for matching purposes.
*/
static final class CharSetMatcher extends StrMatcher {
/** The set of characters to match. */
private final char[] chars;
/**
* Constructor that creates a matcher from a character array.
*
* @param chars the characters to match, must not be null
*/
CharSetMatcher(final char[] chars) {
this.chars = ArraySorter.sort(chars.clone());
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to define a character for matching purposes.
*/
static final class CharMatcher extends StrMatcher {
/** The character to match. */
private final char ch;
/**
* Constructor that creates a matcher that matches a single character.
*
* @param ch the character to match
*/
CharMatcher(final char ch) {
this.ch = ch;
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return ch == buffer[pos] ? 1 : 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to define a set of characters for matching purposes.
*/
static final class StringMatcher extends StrMatcher {
/** The string to match, as a character array. */
private final char[] chars;
/**
* Constructor that creates a matcher from a String.
*
* @param str the string to match, must not be null
*/
StringMatcher(final String str) {
chars = str.toCharArray();
}
/**
* Returns whether or not the given text matches the stored string.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
final int len = chars.length;
if (pos + len > bufferEnd) {
return 0;
}
for (int i = 0; i < chars.length; i++, pos++) {
if (chars[i] != buffer[pos]) {
return 0;
}
}
return len;
}
@Override
public String toString() {
return super.toString() + ' ' + Arrays.toString(chars);
}
}
// -----------------------------------------------------------------------
/**
* Class used to match no characters.
*/
static final class NoMatcher extends StrMatcher {
/**
* Constructs a new instance of {@code NoMatcher}.
*/
NoMatcher() {
}
/**
* Always returns {@code false}.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to match whitespace as per trim().
*/
static final class TrimMatcher extends StrMatcher {
/**
* Constructs a new instance of {@code TrimMatcher}.
*/
TrimMatcher() {
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return buffer[pos] <= 32 ? 1 : 0;
}
}
}

View File

@ -1,7 +1,6 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Protocol;
@ -26,7 +25,7 @@ public class EaglerChannelWrapper extends ChannelWrapper {
super(ctx); super(ctx);
} }
public void setProtocol(EaglerBungeeProtocol protocol) { public void setProtocol(Protocol protocol) {
getHandle().pipeline().get(EaglerMinecraftEncoder.class).setProtocol(protocol); getHandle().pipeline().get(EaglerMinecraftEncoder.class).setProtocol(protocol);
getHandle().pipeline().get(EaglerMinecraftDecoder.class).setProtocol(protocol); getHandle().pipeline().get(EaglerMinecraftDecoder.class).setProtocol(protocol);
} }
@ -41,19 +40,7 @@ public class EaglerChannelWrapper extends ChannelWrapper {
public Protocol getEncodeProtocol() { public Protocol getEncodeProtocol() {
EaglerMinecraftEncoder enc; EaglerMinecraftEncoder enc;
if (this.getHandle() == null || (enc = this.getHandle().pipeline().get(EaglerMinecraftEncoder.class)) == null) return lastProtocol; if (this.getHandle() == null || (enc = this.getHandle().pipeline().get(EaglerMinecraftEncoder.class)) == null) return lastProtocol;
EaglerBungeeProtocol eaglerProtocol = enc.getProtocol(); return (lastProtocol = enc.getProtocol());
switch(eaglerProtocol) {
case GAME:
return (lastProtocol = Protocol.GAME);
case HANDSHAKE:
return (lastProtocol = Protocol.HANDSHAKE);
case LOGIN:
return (lastProtocol = Protocol.LOGIN);
case STATUS:
return (lastProtocol = Protocol.STATUS);
default:
return lastProtocol;
}
} }
public void close(Object o) { public void close(Object o) {

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/** /**
@ -33,11 +34,12 @@ public class EaglerConnectionInstance {
public boolean isRegularHttp = false; public boolean isRegularHttp = false;
public UserConnection userConnection = null; public UserConnection userConnection = null;
public HttpServerQueryHandler queryHandler = null;
public EaglerConnectionInstance(Channel channel) { public EaglerConnectionInstance(Channel channel) {
this.channel = channel; this.channel = channel;
this.creationTime = this.lastServerPingPacket = this.lastClientPingPacket = this.creationTime = this.lastServerPingPacket = this.lastClientPingPacket =
this.lastClientPongPacket = System.currentTimeMillis(); this.lastClientPongPacket = EaglerXBungeeAPIHelper.steadyTimeMillis();
} }
} }

View File

@ -1,24 +1,56 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Collections2;
import gnu.trove.set.TIntSet; import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet; import gnu.trove.set.hash.TIntHashSet;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.NotificationBadgeBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeHideV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsRegisterV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsReleaseV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketRedirectClientV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketSetServerCookieV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Property; import net.md_5.bungee.protocol.Property;
@ -33,7 +65,7 @@ import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.StatusRequest; import net.md_5.bungee.protocol.packet.StatusRequest;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -58,47 +90,84 @@ public class EaglerInitialHandler extends InitialHandler {
} }
} }
private final int gameProtocolVersion; protected final int clientProtocolVersion;
private final String username; protected final int gameProtocolVersion;
private final UUID playerUUID; protected final String clientBrandString;
private final UUID playerUUIDOffline; protected final String clientVersionString;
private final UUID playerUUIDRewrite; protected final UUID clientBrandUUID;
private LoginResult loginResult; protected final String username;
private final InetSocketAddress eaglerAddress; protected final UUID playerUUID;
private final InetSocketAddress virtualHost; protected final UUID playerUUIDOffline;
private final Unsafe eaglerUnsafe; protected final UUID playerUUIDRewrite;
protected final InetSocketAddress eaglerAddress;
protected final InetSocketAddress virtualHost;
protected final Unsafe eaglerUnsafe;
public final SimpleRateLimiter skinLookupRateLimiter; public final SimpleRateLimiter skinLookupRateLimiter;
public final SimpleRateLimiter skinUUIDLookupRateLimiter; public final SimpleRateLimiter skinUUIDLookupRateLimiter;
public final SimpleRateLimiter skinTextureDownloadRateLimiter; public final SimpleRateLimiter skinTextureDownloadRateLimiter;
public final SimpleRateLimiter capeLookupRateLimiter; public final SimpleRateLimiter capeLookupRateLimiter;
public final SimpleRateLimiter voiceConnectRateLimiter; public final SimpleRateLimiter voiceConnectRateLimiter;
public final String origin; protected final String origin;
protected final String userAgent;
public final ClientCertificateHolder clientCertificate; public final ClientCertificateHolder clientCertificate;
public final Set<ClientCertificateHolder> certificatesToSend; public final Set<ClientCertificateHolder> certificatesToSend;
public final TIntSet certificatesSent; public final TIntSet certificatesSent;
public boolean currentFNAWSkinEnableStatus = true; public final EaglerChannelWrapper ch;
public final AtomicBoolean currentFNAWSkinEnableStatus = new AtomicBoolean(true);
public final AtomicBoolean currentFNAWSkinForceStatus = new AtomicBoolean(false);
volatile GameProtocolMessageController messageProtocolController = null;
protected final boolean allowCookie;
protected volatile byte[] cookie;
public volatile SkinPacketVersionCache originalSkin = null;
public volatile GameMessagePacket originalCape = null;
protected final Map<String,byte[]> otherProfileDataFromHanshake;
public boolean isWebViewChannelAllowed = false;
public final AtomicBoolean webViewMessageChannelOpen = new AtomicBoolean(false);
public volatile String webViewMessageChannelName = null;
public final AtomicBoolean hasSentServerInfo = new AtomicBoolean(false);
public final List<GameMessagePacket> serverInfoSendBuffer = new LinkedList<>();
protected BackendRPCSessionHandler backedRPCSessionHandler = null;
protected final AtomicReference<EaglercraftVoiceStatusChangeEvent.EnumVoiceState> lastVoiceState = new AtomicReference<>(
EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
private static final Property[] NO_PROPERTIES = new Property[0]; private static final Property[] NO_PROPERTIES = new Property[0];
public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final ChannelWrapper ch, public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final EaglerChannelWrapper ch,
int gameProtocolVersion, String username, UUID playerUUID, UUID offlineUUID, InetSocketAddress address, int clientProtocolVersion, int gameProtocolVersion, String clientBrandString, String clientVersionString,
String host, String origin, ClientCertificateHolder clientCertificate) { UUID clientBrandUUID, String username, UUID playerUUID, UUID offlineUUID, InetSocketAddress address,
String host, String origin, String userAgent, ClientCertificateHolder clientCertificate,
boolean allowCookie, byte[] cookie, Map<String,byte[]> otherProfileData) {
super(bungee, listener); super(bungee, listener);
this.ch = ch;
this.clientProtocolVersion = clientProtocolVersion;
this.gameProtocolVersion = gameProtocolVersion; this.gameProtocolVersion = gameProtocolVersion;
this.clientBrandString = clientBrandString;
this.clientVersionString = clientVersionString;
this.clientBrandUUID = clientBrandUUID;
this.username = username; this.username = username;
this.playerUUID = playerUUID; this.playerUUID = playerUUID;
this.playerUUIDOffline = offlineUUID; this.playerUUIDOffline = offlineUUID;
this.playerUUIDRewrite = bungee.config.isIpForward() ? playerUUID : offlineUUID; this.playerUUIDRewrite = bungee.config.isIpForward() ? playerUUID : offlineUUID;
this.eaglerAddress = address; this.eaglerAddress = address;
this.origin = origin; this.origin = origin;
this.userAgent = userAgent;
this.skinLookupRateLimiter = new SimpleRateLimiter(); this.skinLookupRateLimiter = new SimpleRateLimiter();
this.skinUUIDLookupRateLimiter = new SimpleRateLimiter(); this.skinUUIDLookupRateLimiter = new SimpleRateLimiter();
this.skinTextureDownloadRateLimiter = new SimpleRateLimiter(); this.skinTextureDownloadRateLimiter = new SimpleRateLimiter();
this.capeLookupRateLimiter = new SimpleRateLimiter(); this.capeLookupRateLimiter = new SimpleRateLimiter();
this.voiceConnectRateLimiter = new SimpleRateLimiter(); this.voiceConnectRateLimiter = new SimpleRateLimiter();
this.allowCookie = allowCookie;
this.cookie = cookie;
this.otherProfileDataFromHanshake = otherProfileData;
this.clientCertificate = clientCertificate; this.clientCertificate = clientCertificate;
this.certificatesToSend = new HashSet(); this.certificatesToSend = new HashSet<>();
this.certificatesSent = new TIntHashSet(); this.certificatesSent = new TIntHashSet();
EaglerBungeeConfig conf = EaglerXBungee.getEagler().getConfig();
SPacketCustomizePauseMenuV4EAG pkt = conf.getPauseMenuConf().getPacket();
this.isWebViewChannelAllowed = pkt != null
&& (pkt.serverInfoEmbedPerms & SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_MESSAGE_API) != 0;
this.backedRPCSessionHandler = conf.getEnableBackendRPCAPI()
? BackendRPCSessionHandler.createForPlayer(this) : null;
if(clientCertificate != null) { if(clientCertificate != null) {
this.certificatesSent.add(clientCertificate.hashCode()); this.certificatesSent.add(clientCertificate.hashCode());
} }
@ -131,6 +200,255 @@ public class EaglerInitialHandler extends InitialHandler {
} }
} }
public GameProtocolMessageController getEaglerMessageController() {
return messageProtocolController;
}
public GamePluginMessageProtocol getEaglerProtocol() {
return messageProtocolController == null ? GamePluginMessageProtocol.getByVersion(clientProtocolVersion)
: messageProtocolController.protocol;
}
public int getEaglerProtocolHandshake() {
return clientProtocolVersion;
}
public void sendEaglerMessage(GameMessagePacket pkt) {
if(messageProtocolController != null) {
try {
messageProtocolController.sendPacket(pkt);
} catch (IOException e) {
this.disconnect(new TextComponent("Failed to write eaglercraft packet! (" + e.toString() + ")"));
}
}else {
throw new IllegalStateException("Race condition detected, messageProtocolController is null! (wait until getEaglerMessageController() does not return null before sending the packet)");
}
}
public boolean getWebViewSupport() {
return getEaglerProtocol().ver >= 4;
}
public void setWebViewChannelAllowed(boolean en) {
isWebViewChannelAllowed = en;
}
public boolean getWebViewChannelAllowed() {
return isWebViewChannelAllowed;
}
public boolean getWebViewMessageChannelOpen() {
return webViewMessageChannelOpen.get();
}
public String getWebViewMessageChannelName() {
return webViewMessageChannelName;
}
public void sendWebViewMessage(int type, byte[] bytes) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(type, bytes));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public void sendWebViewMessage(String str) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(str));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public void sendWebViewMessage(byte[] bin) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(bin));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public EnumWebViewState getWebViewState() {
if(!getWebViewSupport()) {
return EnumWebViewState.NOT_SUPPORTED;
}
if(isWebViewChannelAllowed) {
if(webViewMessageChannelOpen.get()) {
return EnumWebViewState.CHANNEL_OPEN;
}else {
return EnumWebViewState.CHANNEL_CLOSED;
}
}else {
return EnumWebViewState.SERVER_DISABLE;
}
}
public boolean getCookieAllowed() {
return allowCookie;
}
public byte[] getCookieData() {
return allowCookie ? cookie : null;
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), false, true);
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit, boolean revokeQuerySupported) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), revokeQuerySupported, true);
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit, boolean revokeQuerySupported, boolean clientSaveCookieToDisk) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), revokeQuerySupported, clientSaveCookieToDisk);
}
public void setCookieData(byte[] data, long expiresAfterSec, boolean revokeQuerySupported, boolean clientSaveCookieToDisk) {
if(allowCookie) {
if(expiresAfterSec < 0l) {
expiresAfterSec = 0l;
data = null;
}
if(data == null) {
cookie = null;
sendEaglerMessage(new SPacketSetServerCookieV4EAG(null, 01, false, false));
return;
}
if(data.length > 255) {
throw new IllegalArgumentException("Cookie cannot be longer than 255 bytes!");
}
if(expiresAfterSec > 604800l) {
throw new IllegalArgumentException("Cookie cannot be set for longer than 7 days! (tried " + (expiresAfterSec / 604800l) + " days)");
}
cookie = data;
sendEaglerMessage(new SPacketSetServerCookieV4EAG(data, expiresAfterSec, revokeQuerySupported, clientSaveCookieToDisk));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to set a cookie for player \"" + username + "\", but the player has cookies disabled!");
}
}
public void clearCookieData() {
setCookieData(null, 0, false, false);
}
public boolean notificationSupported() {
return clientProtocolVersion >= 4;
}
public void registerNotificationIcon(UUID uuid, PacketImageData imageData) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(
Arrays.asList(new SPacketNotifIconsRegisterV4EAG.CreateIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), imageData))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to register notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void registerNotificationIcons(Map<UUID,PacketImageData> imageDatas) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(
new ArrayList<>(Collections2.transform(imageDatas.entrySet(), (etr) -> {
UUID key = etr.getKey();
return new SPacketNotifIconsRegisterV4EAG.CreateIcon(key.getMostSignificantBits(),
key.getLeastSignificantBits(), etr.getValue());
}))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to register notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void showNotificationBadge(NotificationBadgeBuilder badgeBuilder) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgeBuilder.buildPacket());
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to show notification badges to player \"" + username + "\", but the player has notifications disabled!");
}
}
public void showNotificationBadge(SPacketNotifBadgeShowV4EAG badgePacket) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgePacket);
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to show notification badges to player \"" + username + "\", but the player has notifications disabled!");
}
}
public void hideNotificationBadge(UUID badgeUUID) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(badgeUUID.getMostSignificantBits(), badgeUUID.getLeastSignificantBits()));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to hide notification badges for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void releaseNotificationIcon(UUID uuid) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(
Arrays.asList(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to release notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void releaseNotificationIcons(Collection<UUID> uuids) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(new ArrayList<>(Collections2.transform(uuids,
(etr) -> new SPacketNotifIconsReleaseV4EAG.DestroyIcon(etr.getMostSignificantBits(),
etr.getLeastSignificantBits())))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to release notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public boolean redirectToWebSocketSupported() {
return clientProtocolVersion >= 4;
}
public void redirectPlayerToWebSocket(String serverAddress) {
if(getEaglerProtocol().ver >= 4) {
sendEaglerMessage(new SPacketRedirectClientV4EAG(serverAddress));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to redirect player \"" + username + "\" to a different websocket, but that player's client doesn't support this feature!");
}
}
public BackendRPCSessionHandler getRPCSessionHandler() {
return backedRPCSessionHandler;
}
public boolean getRPCEventSubscribed(EnumSubscribedEvent event) {
return backedRPCSessionHandler != null && backedRPCSessionHandler.isSubscribed(event);
}
public void handleBackendRPCPacket(Server server, byte[] data) {
if(backedRPCSessionHandler != null) {
backedRPCSessionHandler.handleRPCPacket(server, data);
}else {
EaglerXBungee.logger().severe("[" + getSocketAddress().toString() + "]: Server tried to send backend RPC packet to player \"" + username + "\" but this feature is not enabled. Enable it by setting \"enable_backend_rpc_api: true\" in settings.yml");
}
}
public void fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState state) {
EaglercraftVoiceStatusChangeEvent.EnumVoiceState oldState = lastVoiceState.getAndSet(state);
if(state != oldState) {
BungeeCord.getInstance().getPluginManager().callEvent(new EaglercraftVoiceStatusChangeEvent(
EaglerXBungeeAPIHelper.getPlayer(this), getEaglerListenerConfig(), this, oldState, state));
}
}
public String getEaglerBrandString() {
return clientBrandString;
}
public String getEaglerVersionString() {
return clientVersionString;
}
public UUID getClientBrandUUID() {
return clientBrandUUID;
}
public static UUID generateOfflineUUID(byte[] username) { public static UUID generateOfflineUUID(byte[] username) {
String offlinePlayerStr = "OfflinePlayer:"; String offlinePlayerStr = "OfflinePlayer:";
byte[] uuidHashGenerator = new byte[offlinePlayerStr.length() + username.length]; byte[] uuidHashGenerator = new byte[offlinePlayerStr.length() + username.length];
@ -139,16 +457,29 @@ public class EaglerInitialHandler extends InitialHandler {
return UUID.nameUUIDFromBytes(uuidHashGenerator); return UUID.nameUUIDFromBytes(uuidHashGenerator);
} }
void setLoginProfile(LoginResult obj) { private static final Field loginProfileField;
this.loginResult = obj;
static {
try { try {
Field f = InitialHandler.class.getDeclaredField("loginProfile"); loginProfileField = InitialHandler.class.getDeclaredField("loginProfile");
f.setAccessible(true); loginProfileField.setAccessible(true);
f.set(this, obj);
}catch(Throwable t) { }catch(Throwable t) {
throw new RuntimeException("Could not access loginProfile field", t);
} }
} }
void setLoginProfile(LoginResult obj) {
try {
loginProfileField.set(this, obj);
}catch(Throwable t) {
throw new RuntimeException("Could not perform reflection", t);
}
}
public byte[] getOtherProfileDataFromHandshake(String name) {
return otherProfileDataFromHanshake.get(name);
}
@Override @Override
public void handle(PacketWrapper packet) throws Exception { public void handle(PacketWrapper packet) throws Exception {
} }
@ -266,11 +597,6 @@ public class EaglerInitialHandler extends InitialHandler {
return playerUUID.toString().replace("-", ""); return playerUUID.toString().replace("-", "");
} }
@Override
public LoginResult getLoginProfile() {
return loginResult;
}
@Override @Override
public InetSocketAddress getVirtualHost() { public InetSocketAddress getVirtualHost() {
return virtualHost; return virtualHost;
@ -290,7 +616,12 @@ public class EaglerInitialHandler extends InitialHandler {
return origin; return origin;
} }
public String getUserAgent() {
return userAgent;
}
public EaglerListenerConfig getEaglerListenerConfig() { public EaglerListenerConfig getEaglerListenerConfig() {
return (EaglerListenerConfig)getListener(); return (EaglerListenerConfig)getListener();
} }
} }

View File

@ -3,7 +3,6 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.util.List; import java.util.List;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;

View File

@ -11,10 +11,8 @@ import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.ReferenceCountUtil;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Protocol;
@ -36,7 +34,7 @@ import net.md_5.bungee.protocol.ProtocolConstants.Direction;
* *
*/ */
public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFrame> { public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFrame> {
private EaglerBungeeProtocol protocol; private Protocol protocol;
private final boolean server; private final boolean server;
private int protocolVersion; private int protocolVersion;
private static Constructor<PacketWrapper> packetWrapperConstructor = null; private static Constructor<PacketWrapper> packetWrapperConstructor = null;
@ -47,27 +45,13 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
return; return;
} }
EaglerConnectionInstance con = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get(); EaglerConnectionInstance con = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(frame instanceof BinaryWebSocketFrame) { if(frame instanceof BinaryWebSocketFrame) {
BinaryWebSocketFrame in = (BinaryWebSocketFrame) frame; BinaryWebSocketFrame in = (BinaryWebSocketFrame) frame;
ByteBuf buf = in.content(); ByteBuf buf = in.content();
buf.markReaderIndex(); buf.markReaderIndex();
int pktId = DefinedPacket.readVarInt(buf); int pktId = DefinedPacket.readVarInt(buf);
DefinedPacket pkt = EaglerProtocolAccessProxy.createPacket(protocol, protocolVersion, pktId, server); DefinedPacket pkt = EaglerProtocolAccessProxy.createPacket(protocol, protocolVersion, pktId, server);
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
if(pkt != null) { if(pkt != null) {
pkt.read(buf, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion); pkt.read(buf, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
if(buf.isReadable()) { if(buf.isReadable()) {
@ -75,11 +59,11 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
pkt.getClass().getSimpleName() + " had extra bytes! (" + buf.readableBytes() + ")"); pkt.getClass().getSimpleName() + " had extra bytes! (" + buf.readableBytes() + ")");
}else { }else {
buf.resetReaderIndex(); buf.resetReaderIndex();
out.add(this.wrapPacket(pkt, buf, bungeeProtocol)); out.add(this.wrapPacket(pkt, buf, protocol));
} }
}else { }else {
buf.resetReaderIndex(); buf.resetReaderIndex();
out.add(this.wrapPacket(null, buf, bungeeProtocol)); out.add(this.wrapPacket(null, buf, protocol));
} }
}else if(frame instanceof PingWebSocketFrame) { }else if(frame instanceof PingWebSocketFrame) {
if(millis - con.lastClientPingPacket > 500l) { if(millis - con.lastClientPingPacket > 500l) {
@ -93,17 +77,17 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
} }
} }
public EaglerMinecraftDecoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) { public EaglerMinecraftDecoder(Protocol protocol, boolean server, int protocolVersion) {
this.protocol = protocol; this.protocol = protocol;
this.server = server; this.server = server;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
} }
public void setProtocol(final EaglerBungeeProtocol protocol) { public void setProtocol(Protocol protocol) {
this.protocol = protocol; this.protocol = protocol;
} }
public void setProtocolVersion(final int protocolVersion) { public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
} }

View File

@ -7,8 +7,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants.Direction; import net.md_5.bungee.protocol.ProtocolConstants.Direction;
@ -30,32 +28,18 @@ import net.md_5.bungee.protocol.ProtocolConstants.Direction;
*/ */
public class EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacket> { public class EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacket> {
private EaglerBungeeProtocol protocol; private Protocol protocol;
private boolean server; private boolean server;
private int protocolVersion; private int protocolVersion;
private static Method meth = null; private static Method meth = null;
@Override @Override
protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, List<Object> out) throws Exception {
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
ByteBuf buf = ctx.alloc().buffer(); ByteBuf buf = ctx.alloc().buffer();
int pk = EaglerProtocolAccessProxy.getPacketId(protocol, protocolVersion, msg, server); int pk = EaglerProtocolAccessProxy.getPacketId(protocol, protocolVersion, msg, server);
DefinedPacket.writeVarInt(pk, buf); DefinedPacket.writeVarInt(pk, buf);
try { try {
msg.write(buf, bungeeProtocol, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion); msg.write(buf, protocol, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
} catch (NoSuchMethodError e) { } catch (NoSuchMethodError e) {
try { try {
if (meth == null) { if (meth == null) {
@ -73,21 +57,21 @@ public class EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacke
out.add(new BinaryWebSocketFrame(buf)); out.add(new BinaryWebSocketFrame(buf));
} }
public EaglerMinecraftEncoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) { public EaglerMinecraftEncoder(Protocol protocol, boolean server, int protocolVersion) {
this.protocol = protocol; this.protocol = protocol;
this.server = server; this.server = server;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
} }
public void setProtocol(final EaglerBungeeProtocol protocol) { public void setProtocol(Protocol protocol) {
this.protocol = protocol; this.protocol = protocol;
} }
public void setProtocolVersion(final int protocolVersion) { public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
} }
public EaglerBungeeProtocol getProtocol() { public Protocol getProtocol() {
return this.protocol; return this.protocol;
} }

View File

@ -4,12 +4,18 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.commons.lang3.ArrayUtils;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelException; import io.netty.channel.ChannelException;
@ -25,11 +31,21 @@ import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensio
import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameServerExtensionHandshaker; import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameServerExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker; import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketEnableFNAWSkinsEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketUpdateCertEAG;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.scheduler.BungeeScheduler;
/** /**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
@ -54,11 +70,13 @@ public class EaglerPipeline {
public static final AttributeKey<InetAddress> REAL_ADDRESS = AttributeKey.valueOf("RealAddress"); public static final AttributeKey<InetAddress> REAL_ADDRESS = AttributeKey.valueOf("RealAddress");
public static final AttributeKey<String> HOST = AttributeKey.valueOf("Host"); public static final AttributeKey<String> HOST = AttributeKey.valueOf("Host");
public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("Origin"); public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("Origin");
public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("UserAgent");
public static final int LOW_MARK = Integer.getInteger("net.md_5.bungee.low_mark", 524288); public static final int LOW_MARK = Integer.getInteger("net.md_5.bungee.low_mark", 524288);
public static final int HIGH_MARK = Integer.getInteger("net.md_5.bungee.high_mark", 2097152); public static final int HIGH_MARK = Integer.getInteger("net.md_5.bungee.high_mark", 2097152);
public static final WriteBufferWaterMark MARK = new WriteBufferWaterMark(LOW_MARK, HIGH_MARK); public static final WriteBufferWaterMark MARK = new WriteBufferWaterMark(LOW_MARK, HIGH_MARK);
public static final Collection<Channel> openChannels = new LinkedList(); public static final Collection<Channel> openChannels = new LinkedList<>();
public static final Set<UserConnection> waitingServerConnections = new HashSet<>();
public static final String UPDATE_CERT_CHANNEL = "EAG|UpdateCert-1.8"; public static final String UPDATE_CERT_CHANNEL = "EAG|UpdateCert-1.8";
@ -74,7 +92,7 @@ public class EaglerPipeline {
long httpTimeout = conf.getBuiltinHttpServerTimeout(); long httpTimeout = conf.getBuiltinHttpServerTimeout();
List<Channel> channelsList; List<Channel> channelsList;
synchronized(openChannels) { synchronized(openChannels) {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
Iterator<Channel> channelIterator = openChannels.iterator(); Iterator<Channel> channelIterator = openChannels.iterator();
while(channelIterator.hasNext()) { while(channelIterator.hasNext()) {
Channel c = channelIterator.next(); Channel c = channelIterator.next();
@ -82,7 +100,15 @@ public class EaglerPipeline {
long handshakeTimeoutForConnection = 500l; long handshakeTimeoutForConnection = 500l;
if(i.isRegularHttp) handshakeTimeoutForConnection = httpTimeout; if(i.isRegularHttp) handshakeTimeoutForConnection = httpTimeout;
else if(i.isWebSocket) handshakeTimeoutForConnection = handshakeTimeout; else if(i.isWebSocket) handshakeTimeoutForConnection = handshakeTimeout;
if(i == null || (!i.hasBeenForwarded && millis - i.creationTime > handshakeTimeoutForConnection) boolean hasTimeout = !i.hasBeenForwarded;
if(i.queryHandler != null) {
long l = i.queryHandler.getMaxAge();
hasTimeout = l != -1l;
if(hasTimeout) {
handshakeTimeoutForConnection = l;
}
}
if((hasTimeout && millis - i.creationTime > handshakeTimeoutForConnection)
|| millis - i.lastClientPongPacket > keepAliveTimeout || !c.isActive()) { || millis - i.lastClientPongPacket > keepAliveTimeout || !c.isActive()) {
if(c.isActive()) { if(c.isActive()) {
c.close(); c.close();
@ -102,7 +128,105 @@ public class EaglerPipeline {
} }
} }
} }
channelsList = new ArrayList(openChannels); channelsList = new ArrayList<>(openChannels);
}
List<UserConnection> readyServerConnections = null;
synchronized(waitingServerConnections) {
Iterator<UserConnection> connIterator = waitingServerConnections.iterator();
while(connIterator.hasNext()) {
UserConnection userCon = connIterator.next();
if(userCon.isConnected()) {
ServerConnection serverCon = userCon.getServer();
if(serverCon != null) {
if(readyServerConnections == null) {
readyServerConnections = new ArrayList<>(4);
}
readyServerConnections.add(userCon);
connIterator.remove();
}
}else {
connIterator.remove();
}
}
}
if(readyServerConnections != null) {
for(int i = 0, l = readyServerConnections.size(); i < l; ++i) {
handleServerConnectionReady(readyServerConnections.get(i));
}
}
boolean updateLoop = !conf.getUpdateConfig().isBlockAllClientUpdates();
final AtomicInteger sizeTracker = updateLoop ? new AtomicInteger(0) : null;
final int rateLimitParam = conf.getUpdateConfig().getCertPacketDataRateLimit() / 4;
final int serverInfoSendRate = Math.max(conf.getPauseMenuConf().getInfoSendRate(), 1);
BungeeScheduler sched = BungeeCord.getInstance().getScheduler();
for(Channel c : channelsList) {
EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
final EaglerInitialHandler i = (EaglerInitialHandler)conn.userConnection.getPendingConnection();
boolean certToSend = false;
if(updateLoop) {
synchronized(i.certificatesToSend) {
if(!i.certificatesToSend.isEmpty()) {
certToSend = true;
}
}
}
boolean serverInfoToSend = false;
synchronized(i.serverInfoSendBuffer) {
if(!i.serverInfoSendBuffer.isEmpty()) {
serverInfoToSend = true;
}
}
if(certToSend || serverInfoToSend) {
final boolean do_certToSend = certToSend;
final boolean do_serverInfoToSend = serverInfoToSend;
sched.runAsync(EaglerXBungee.getEagler(), () -> {
if(do_certToSend) {
ClientCertificateHolder certHolder = null;
synchronized(i.certificatesToSend) {
if(i.certificatesToSend.size() > 0) {
Iterator<ClientCertificateHolder> itr = i.certificatesToSend.iterator();
certHolder = itr.next();
itr.remove();
}
}
if(certHolder != null && sizeTracker.getAndAdd(certHolder.data.length) < rateLimitParam) {
int identityHash = certHolder.hashCode();
boolean bb;
synchronized(i.certificatesSent) {
bb = i.certificatesSent.add(identityHash);
}
if(bb) {
i.sendEaglerMessage(new SPacketUpdateCertEAG(certHolder.data));
}
}
}
if(do_serverInfoToSend) {
List<GameMessagePacket> toSend = i.serverInfoSendBuffer;
synchronized(toSend) {
if(!toSend.isEmpty()) {
try {
if(serverInfoSendRate == 1) {
i.getEaglerMessageController().sendPacketImmediately(toSend.remove(0));
}else {
for(int j = 0; j < serverInfoSendRate; ++j) {
if(!toSend.isEmpty()) {
i.getEaglerMessageController().sendPacketImmediately(toSend.remove(0));
}else {
break;
}
}
}
}catch(Throwable t) {
log.log(Level.SEVERE, "Exception in thread \"" + Thread.currentThread().getName() + "\"!", t);
}
}
}
}
});
}
} }
for(EaglerListenerConfig lst : conf.getServerListeners()) { for(EaglerListenerConfig lst : conf.getServerListeners()) {
HttpWebServer srv = lst.getWebServer(); HttpWebServer srv = lst.getWebServer();
@ -115,39 +239,6 @@ public class EaglerPipeline {
} }
} }
} }
if(!conf.getUpdateConfig().isBlockAllClientUpdates()) {
int sizeTracker = 0;
for(Channel c : channelsList) {
EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
EaglerInitialHandler i = (EaglerInitialHandler)conn.userConnection.getPendingConnection();
ClientCertificateHolder certHolder = null;
synchronized(i.certificatesToSend) {
if(i.certificatesToSend.size() > 0) {
Iterator<ClientCertificateHolder> itr = i.certificatesToSend.iterator();
certHolder = itr.next();
itr.remove();
}
}
if(certHolder != null) {
int identityHash = certHolder.hashCode();
boolean bb;
synchronized(i.certificatesSent) {
bb = i.certificatesSent.add(identityHash);
}
if(bb) {
conn.userConnection.sendData(UPDATE_CERT_CHANNEL, certHolder.data);
sizeTracker += certHolder.data.length;
if(sizeTracker > (conf.getUpdateConfig().getCertPacketDataRateLimit() / 4)) {
break;
}
}
}
}
EaglerUpdateSvc.updateTick();
}
}catch(Throwable t) { }catch(Throwable t) {
log.severe("Exception in thread \"" + Thread.currentThread().getName() + "\"! " + t.toString()); log.severe("Exception in thread \"" + Thread.currentThread().getName() + "\"! " + t.toString());
t.printStackTrace(); t.printStackTrace();
@ -195,4 +286,33 @@ public class EaglerPipeline {
} }
} }
public static void addServerConnectListener(UserConnection player) {
synchronized(waitingServerConnections) {
waitingServerConnections.add(player);
}
}
private static void handleServerConnectionReady(UserConnection userConnection) {
try {
ServerConnection server = userConnection.getServer();
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME_READY, ArrayUtils.EMPTY_BYTE_ARRAY);
if(userConnection.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler) userConnection.getPendingConnection();
ServerInfo sv = server.getInfo();
EaglerXBungee plugin = EaglerXBungee.getEagler();
boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere()
&& !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName());
if(fnawSkins != handler.currentFNAWSkinEnableStatus.getAndSet(fnawSkins)) {
handler.sendEaglerMessage(new SPacketEnableFNAWSkinsEAG(fnawSkins, false));
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(userConnection, sv);
}
}
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Failed to process server connection ready handler for player \""
+ userConnection.getName() + "\"", t);
}
}
} }

View File

@ -0,0 +1,64 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerProtocolAccessProxy {
private static final Field fieldToClient;
private static final Field fieldToServer;
private static final Method methodGetId;
private static final Method methodCreatePacket;
static {
try {
fieldToClient = Protocol.class.getDeclaredField("TO_CLIENT");
fieldToClient.setAccessible(true);
fieldToServer = Protocol.class.getDeclaredField("TO_SERVER");
fieldToServer.setAccessible(true);
methodGetId = Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class);
methodGetId.setAccessible(true);
methodCreatePacket = Protocol.DirectionData.class.getDeclaredMethod("createPacket", int.class, int.class);
methodCreatePacket.setAccessible(true);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
public static int getPacketId(Protocol protocol, int protocolVersion, DefinedPacket pkt, boolean server) {
try {
Object prot = server ? fieldToClient.get(protocol) : fieldToServer.get(protocol);
return (int)methodGetId.invoke(prot, pkt.getClass(), protocolVersion);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
public static DefinedPacket createPacket(Protocol protocol, int protocolVersion, int packetId, boolean server) {
try {
Object prot = server ? fieldToClient.get(protocol) : fieldToServer.get(protocol);
return (DefinedPacket) methodCreatePacket.invoke(prot, packetId, protocolVersion);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.SHA1Digest; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
@ -43,9 +44,9 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
*/ */
public class EaglerUpdateSvc { public class EaglerUpdateSvc {
private static final List<ClientCertificateHolder> certs = new ArrayList(); private static final List<ClientCertificateHolder> certs = new ArrayList<>();
private static final Map<String,CachedClientCertificate> certsCache = new HashMap(); private static final Map<String,CachedClientCertificate> certsCache = new HashMap<>();
private static final Set<String> deadURLS = new HashSet(); private static final Set<String> deadURLS = new HashSet<>();
private static class CachedClientCertificate { private static class CachedClientCertificate {
private final ClientCertificateHolder cert; private final ClientCertificateHolder cert;
@ -61,7 +62,7 @@ public class EaglerUpdateSvc {
public static void updateTick() { public static void updateTick() {
Logger log = EaglerXBungee.logger(); Logger log = EaglerXBungee.logger();
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
EaglerUpdateConfig conf = EaglerXBungee.getEagler().getConfig().getUpdateConfig(); EaglerUpdateConfig conf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
if(conf.isDownloadLatestCerts() && millis - lastDownload > (long)conf.getCheckForUpdatesEvery() * 1000l) { if(conf.isDownloadLatestCerts() && millis - lastDownload > (long)conf.getCheckForUpdatesEvery() * 1000l) {
lastDownload = millis; lastDownload = millis;
@ -72,7 +73,7 @@ public class EaglerUpdateSvc {
log.severe("Uncaught exception downloading certificates!"); log.severe("Uncaught exception downloading certificates!");
t.printStackTrace(); t.printStackTrace();
} }
millis = System.currentTimeMillis(); millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
} }
if(conf.isEnableEagcertFolder() && millis - lastEnumerate > 5000l) { if(conf.isEnableEagcertFolder() && millis - lastEnumerate > 5000l) {
lastEnumerate = millis; lastEnumerate = millis;
@ -95,7 +96,7 @@ public class EaglerUpdateSvc {
return; return;
} }
} }
Set<String> filenames = new HashSet(); Set<String> filenames = new HashSet<>();
for(String str : conf.getDownloadCertURLs()) { for(String str : conf.getDownloadCertURLs()) {
try { try {
URL url = new URL(str); URL url = new URL(str);
@ -179,7 +180,7 @@ public class EaglerUpdateSvc {
} }
boolean dirty = false; boolean dirty = false;
File[] dirList = eagcert.listFiles(); File[] dirList = eagcert.listFiles();
Set<String> existingFiles = new HashSet(); Set<String> existingFiles = new HashSet<>();
for(int i = 0; i < dirList.length; ++i) { for(int i = 0; i < dirList.length; ++i) {
File f = dirList[i]; File f = dirList[i];
String n = f.getName(); String n = f.getName();

View File

@ -23,11 +23,14 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebSocketOpenEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRateLimiter; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRateLimiter;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpMemoryCache; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpMemoryCache;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.event.ClientConnectEvent;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -63,12 +66,20 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
String rateLimitHost = null; String rateLimitHost = null;
SocketAddress addr;
if(conf.isForwardIp()) { if(conf.isForwardIp()) {
String str = headers.get(conf.getForwardIpHeader()); String str = headers.get(conf.getForwardIpHeader());
if(str != null) { if(str != null) {
rateLimitHost = str.split(",", 2)[0]; rateLimitHost = str.split(",", 2)[0];
try { try {
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(InetAddress.getByName(rateLimitHost)); InetAddress inetAddr = InetAddress.getByName(rateLimitHost);
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
addr = new InetSocketAddress(inetAddr, ((InetSocketAddress)addr).getPort());
}else {
addr = new InetSocketAddress(inetAddr, 0);
}
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(inetAddr);
}catch(UnknownHostException ex) { }catch(UnknownHostException ex) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Connected with an invalid '" + conf.getForwardIpHeader() + "' header, disconnecting..."); EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Connected with an invalid '" + conf.getForwardIpHeader() + "' header, disconnecting...");
ctx.close(); ctx.close();
@ -80,7 +91,7 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
}else { }else {
SocketAddress addr = ctx.channel().remoteAddress(); addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) { if(addr instanceof InetSocketAddress) {
rateLimitHost = ((InetSocketAddress) addr).getAddress().getHostAddress(); rateLimitHost = ((InetSocketAddress) addr).getAddress().getHostAddress();
} }
@ -98,6 +109,12 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
ClientConnectEvent evt = BungeeCord.getInstance().getPluginManager().callEvent(new ClientConnectEvent(addr, conf));
if(evt.isCancelled()) {
ctx.close();
return;
}
if(headers.get(HttpHeaderNames.CONNECTION) != null && headers.get(HttpHeaderNames.CONNECTION).toLowerCase().contains("upgrade") && if(headers.get(HttpHeaderNames.CONNECTION) != null && headers.get(HttpHeaderNames.CONNECTION).toLowerCase().contains("upgrade") &&
"websocket".equalsIgnoreCase(headers.get(HttpHeaderNames.UPGRADE))) { "websocket".equalsIgnoreCase(headers.get(HttpHeaderNames.UPGRADE))) {
@ -105,10 +122,18 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
if(origin != null) { if(origin != null) {
ctx.channel().attr(EaglerPipeline.ORIGIN).set(origin); ctx.channel().attr(EaglerPipeline.ORIGIN).set(origin);
} }
String userAgent = headers.get(HttpHeaderNames.USER_AGENT);
//TODO: origin blacklist if(userAgent != null) {
ctx.channel().attr(EaglerPipeline.USER_AGENT).set(userAgent);
}
if(ipRateLimit == RateLimitStatus.OK) { if(ipRateLimit == RateLimitStatus.OK) {
EaglercraftWebSocketOpenEvent evt2 = new EaglercraftWebSocketOpenEvent(ctx.channel(), conf, rateLimitHost, origin, userAgent);
BungeeCord.getInstance().getPluginManager().callEvent(evt2);
if(evt2.isCancelled()) {
ctx.close();
return;
}
ctx.channel().attr(EaglerPipeline.HOST).set(headers.get(HttpHeaderNames.HOST)); ctx.channel().attr(EaglerPipeline.HOST).set(headers.get(HttpHeaderNames.HOST));
ctx.pipeline().replace(this, "HttpWebSocketHandler", new HttpWebSocketHandler(conf)); ctx.pipeline().replace(this, "HttpWebSocketHandler", new HttpWebSocketHandler(conf));
} }

View File

@ -72,6 +72,7 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
private boolean acceptBinaryPacket = false; private boolean acceptBinaryPacket = false;
private boolean hasClosed = false; private boolean hasClosed = false;
private boolean keepAlive = false; private boolean keepAlive = false;
private long maxAge = -1l;
public void beginHandleQuery(EaglerListenerConfig conf, ChannelHandlerContext context, String accept) { public void beginHandleQuery(EaglerListenerConfig conf, ChannelHandlerContext context, String accept) {
this.conf = conf; this.conf = conf;
@ -188,6 +189,14 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
return accept; return accept;
} }
public String getOrigin() {
return context.channel().attr(EaglerPipeline.ORIGIN).get();
}
public String getUserAgent() {
return context.channel().attr(EaglerPipeline.USER_AGENT).get();
}
public void sendStringResponse(String type, String str) { public void sendStringResponse(String type, String str) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createStringResponse(accept, str).toString())); context.writeAndFlush(new TextWebSocketFrame(QueryManager.createStringResponse(accept, str).toString()));
} }
@ -220,6 +229,14 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
return keepAlive; return keepAlive;
} }
public long getMaxAge() {
return maxAge;
}
public void setMaxAge(long millis) {
this.maxAge = millis;
}
protected abstract void begin(String queryType); protected abstract void begin(String queryType);
protected abstract void processString(String str); protected abstract void processString(String str);

View File

@ -10,12 +10,15 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
@ -39,10 +42,11 @@ import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GenericFutureListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftClientBrandEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthCookieEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthMethod;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthResponse;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftMOTDEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftMOTDEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterCapeEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterCapeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterSkinEvent; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterSkinEvent;
@ -55,12 +59,14 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRate
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.MOTDQueryHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.MOTDQueryHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.AbstractReconnectHandler; import net.md_5.bungee.api.AbstractReconnectHandler;
@ -73,9 +79,9 @@ import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.connection.UpstreamBridge; import net.md_5.bungee.connection.UpstreamBridge;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.protocol.Property; import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
@ -103,17 +109,19 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private int clientProtocolVersion = -1; private int clientProtocolVersion = -1;
private boolean isProtocolExchanged = false; private boolean isProtocolExchanged = false;
private int gameProtocolVersion = -1; private int gameProtocolVersion = -1;
private CharSequence clientBrandString; private String clientBrandString;
private CharSequence clientVersionString; private String clientVersionString;
private CharSequence clientUsername; private String clientUsername;
private UUID clientUUID; private UUID clientUUID;
private UUID offlineUUID; private UUID offlineUUID;
private CharSequence clientRequestedServer; private String clientRequestedServer;
private boolean clientAuth; private boolean clientAuth;
private byte[] clientAuthUsername; private byte[] clientAuthUsername;
private byte[] clientAuthPassword; private byte[] clientAuthPassword;
private boolean clientEnableCookie;
private byte[] clientCookieData;
private EaglercraftIsAuthRequiredEvent authRequireEvent; private EaglercraftIsAuthRequiredEvent authRequireEvent;
private final Map<String, byte[]> profileData = new HashMap(); private final Map<String, byte[]> profileData = new HashMap<>();
private boolean hasFirstPacket = false; private boolean hasFirstPacket = false;
private boolean hasBinaryConnection = false; private boolean hasBinaryConnection = false;
private boolean connectionClosed = false; private boolean connectionClosed = false;
@ -122,6 +130,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private Property texturesOverrideProperty; private Property texturesOverrideProperty;
private boolean overrideEaglerToVanillaSkins; private boolean overrideEaglerToVanillaSkins;
private static final Set<String> profileDataStandard = Sets.newHashSet(
"skin_v1", "skin_v2", "cape_v1", "update_cert_v1", "brand_uuid_v1");
public HttpWebSocketHandler(EaglerListenerConfig conf) { public HttpWebSocketHandler(EaglerListenerConfig conf) {
this.conf = conf; this.conf = conf;
} }
@ -262,10 +273,10 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Please update your client to register on this server!") sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Please update your client to register on this server!")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
}else if(buffer.readUnsignedByte() != minecraftProtocolVersion) { }else if(buffer.readUnsignedByte() != minecraftProtocolVersion || !conf.isAllowV3()) {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true; connectionClosed = true;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH); buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH);
buf.writeByte(1); buf.writeByte(1);
buf.writeByte(1); buf.writeByte(1);
@ -277,31 +288,37 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
}else if(eaglerLegacyProtocolVersion == 2) { }else if(eaglerLegacyProtocolVersion == 2) {
int minProtVers = Integer.MAX_VALUE;
int maxProtVers = -1;
boolean hasV2InList = false;
boolean hasV3InList = false;
int minGameVers = Integer.MAX_VALUE; //make sure to update VersionQueryHandler too
int maxGameVers = -1; int minServerSupported = conf.isAllowV3() ? 2 : 4;
boolean has47InList = false; int maxServerSupported = conf.isAllowV4() ? 4 : 3;
int minAvailableProtVers = Integer.MAX_VALUE;
int maxAvailableProtVers = Integer.MIN_VALUE;
int minSupportedProtVers = Integer.MAX_VALUE;
int maxSupportedProtVers = Integer.MIN_VALUE;
int cnt = buffer.readUnsignedShort(); int cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) { for(int i = 0; i < cnt; ++i) {
int j = buffer.readUnsignedShort(); int j = buffer.readUnsignedShort();
if(j == 2) { if(j > maxAvailableProtVers) {
hasV2InList = true; maxAvailableProtVers = j;
} }
if(j == 3) { if(j < minAvailableProtVers) {
hasV3InList = true; minAvailableProtVers = j;
} }
if(j > maxProtVers) { if(j >= minServerSupported && j <= maxServerSupported) {
maxProtVers = j; if(j > maxSupportedProtVers) {
maxSupportedProtVers = j;
} }
if(j < minProtVers) { if(j < minSupportedProtVers) {
minProtVers = j; minSupportedProtVers = j;
} }
} }
}
int minGameVers = Integer.MAX_VALUE;
int maxGameVers = -1;
boolean has47InList = false;
cnt = buffer.readUnsignedShort(); cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) { for(int i = 0; i < cnt; ++i) {
@ -317,34 +334,41 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
} }
if(minProtVers == Integer.MAX_VALUE || minGameVers == Integer.MAX_VALUE) { if(maxAvailableProtVers == Integer.MIN_VALUE || maxGameVers == Integer.MIN_VALUE) {
throw new IOException(); throw new IOException();
} }
boolean versMisMatch = false; boolean versMisMatch = false;
boolean isServerProbablyOutdated = false; boolean isServerProbablyOutdated = false;
boolean isClientProbablyOutdated = false; boolean isClientProbablyOutdated = false;
if(!hasV2InList && !hasV3InList) { if(maxSupportedProtVers == Integer.MIN_VALUE) {
clientProtocolVersion = maxAvailableProtVers < 3 ? 2 : 3;
versMisMatch = true; versMisMatch = true;
isServerProbablyOutdated = minProtVers > 3 && maxProtVers > 3; //make sure to update VersionQueryHandler too isServerProbablyOutdated = minAvailableProtVers > maxServerSupported && maxAvailableProtVers > maxServerSupported;
isClientProbablyOutdated = minProtVers < 2 && maxProtVers < 2; isClientProbablyOutdated = minAvailableProtVers < minServerSupported && maxAvailableProtVers < minServerSupported;
}else if(!has47InList) { }else if(!has47InList) {
clientProtocolVersion = 3;
versMisMatch = true; versMisMatch = true;
isServerProbablyOutdated = minGameVers > minecraftProtocolVersion && maxGameVers > minecraftProtocolVersion; isServerProbablyOutdated = minGameVers > minecraftProtocolVersion && maxGameVers > minecraftProtocolVersion;
isClientProbablyOutdated = minGameVers < minecraftProtocolVersion && maxGameVers < minecraftProtocolVersion; isClientProbablyOutdated = minGameVers < minecraftProtocolVersion && maxGameVers < minecraftProtocolVersion;
}else {
clientProtocolVersion = maxSupportedProtVers;
} }
clientProtocolVersion = hasV3InList ? 3 : 2;
if(versMisMatch) { if(versMisMatch) {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true; connectionClosed = true;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH); buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH);
buf.writeShort((conf.isAllowV3() ? 2 : 0) + (conf.isAllowV4() ? 1 : 0));
if(conf.isAllowV3()) {
buf.writeShort(2); buf.writeShort(2);
buf.writeShort(2); // want v2 or v3
buf.writeShort(3); buf.writeShort(3);
}
if(conf.isAllowV4()) {
buf.writeShort(4);
}
buf.writeShort(1); buf.writeShort(1);
buf.writeShort(minecraftProtocolVersion); // want game version 47 buf.writeShort(minecraftProtocolVersion); // want game version 47
@ -362,9 +386,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
int strlen = buffer.readUnsignedByte(); int strlen = buffer.readUnsignedByte();
CharSequence eaglerBrand = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII); String eaglerBrand = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedByte(); strlen = buffer.readUnsignedByte();
CharSequence eaglerVersionString = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII); String eaglerVersionString = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
if(eaglerLegacyProtocolVersion >= 2) { if(eaglerLegacyProtocolVersion >= 2) {
clientAuth = buffer.readBoolean(); clientAuth = buffer.readBoolean();
@ -394,6 +418,19 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
} }
EaglercraftClientBrandEvent brandEvent = new EaglercraftClientBrandEvent(eaglerBrand, eaglerVersionString,
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), clientProtocolVersion, addr);
eaglerXBungee.getProxy().getPluginManager().callEvent(brandEvent);
if(brandEvent.isCancelled()) {
BaseComponent kickReason = brandEvent.getMessage();
if(kickReason == null) {
kickReason = new TextComponent("End of stream");
}
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, kickReason)
.addListener(ChannelFutureListener.CLOSE);
return;
}
final boolean final_useSnapshotFallbackProtocol = useSnapshotFallbackProtocol; final boolean final_useSnapshotFallbackProtocol = useSnapshotFallbackProtocol;
Runnable continueThread = () -> { Runnable continueThread = () -> {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_VERSION; clientLoginState = HandshakePacketTypes.STATE_CLIENT_VERSION;
@ -401,7 +438,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientBrandString = eaglerBrand; clientBrandString = eaglerBrand;
clientVersionString = eaglerVersionString; clientVersionString = eaglerVersionString;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_VERSION); buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_VERSION);
if(final_useSnapshotFallbackProtocol) { if(final_useSnapshotFallbackProtocol) {
@ -426,6 +463,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int meth = getAuthMethodId(authRequireEvent.getUseAuthType()); int meth = getAuthMethodId(authRequireEvent.getUseAuthType());
if(meth == -1) { if(meth == -1) {
buf.release();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Unsupported authentication method resolved") sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Unsupported authentication method resolved")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, unsupported AuthMethod: " + authRequireEvent.getUseAuthType()); EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, unsupported AuthMethod: " + authRequireEvent.getUseAuthType());
@ -459,7 +497,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
AuthResponse resp = authRequireEvent.getAuthRequired(); EaglercraftIsAuthRequiredEvent.AuthResponse resp = authRequireEvent.getAuthRequired();
if(resp == null) { if(resp == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not handled") sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not handled")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
@ -467,13 +505,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
if(resp == AuthResponse.DENY) { if(resp == EaglercraftIsAuthRequiredEvent.AuthResponse.DENY) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, authRequireEvent.getKickMessage()) sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, authRequireEvent.getKickMessage())
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
} }
AuthMethod type = authRequireEvent.getUseAuthType(); EaglercraftIsAuthRequiredEvent.AuthMethod type = authRequireEvent.getUseAuthType();
if(type == null) { if(type == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not fully handled") sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not fully handled")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
@ -489,7 +527,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
if(!clientAuth && resp == AuthResponse.REQUIRE) { if(!clientAuth && resp == EaglercraftIsAuthRequiredEvent.AuthResponse.REQUIRE) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED, sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED,
HandshakePacketTypes.AUTHENTICATION_REQUIRED + " [" + typeId + "] " + authRequireEvent.getAuthMessage()) HandshakePacketTypes.AUTHENTICATION_REQUIRED + " [" + typeId + "] " + authRequireEvent.getAuthMessage())
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
@ -535,10 +573,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientLoginState = HandshakePacketTypes.STATE_STALLING; clientLoginState = HandshakePacketTypes.STATE_STALLING;
int strlen = buffer.readUnsignedByte(); int strlen = buffer.readUnsignedByte();
clientUsername = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII); clientUsername = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
String usrs = clientUsername.toString(); if(!clientUsername.equals(clientUsername.replaceAll("[^A-Za-z0-9_]", "_"))) {
if(!usrs.equals(usrs.replaceAll("[^A-Za-z0-9_]", "_").trim())) {
sendLoginDenied(ctx, "Invalid characters in username") sendLoginDenied(ctx, "Invalid characters in username")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
@ -566,11 +603,28 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientUUID = offlineUUID = EaglerInitialHandler.generateOfflineUUID(clientAuthUsername); clientUUID = offlineUUID = EaglerInitialHandler.generateOfflineUUID(clientAuthUsername);
strlen = buffer.readUnsignedByte(); strlen = buffer.readUnsignedByte();
clientRequestedServer = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII); clientRequestedServer = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedByte(); strlen = buffer.readUnsignedByte();
clientAuthPassword = new byte[strlen]; clientAuthPassword = new byte[strlen];
buffer.readBytes(clientAuthPassword); buffer.readBytes(clientAuthPassword);
if(clientProtocolVersion >= 4) {
clientEnableCookie = buffer.readBoolean();
strlen = buffer.readUnsignedByte();
if(clientEnableCookie && strlen > 0) {
clientCookieData = new byte[strlen];
buffer.readBytes(clientCookieData);
}else {
if(strlen > 0) {
throw new IllegalArgumentException("Unexpected cookie");
}
clientCookieData = null;
}
}else {
clientEnableCookie = false;
clientCookieData = null;
}
if(buffer.isReadable()) { if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long"); throw new IllegalArgumentException("Packet too long");
} }
@ -578,16 +632,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
Runnable continueThread = () -> { Runnable continueThread = () -> {
final BungeeCord bungee = BungeeCord.getInstance(); final BungeeCord bungee = BungeeCord.getInstance();
String usernameStr = clientUsername.toString(); final ProxiedPlayer oldName = bungee.getPlayer(clientUsername);
final ProxiedPlayer oldName = bungee.getPlayer(usernameStr);
if (oldName != null) { if (oldName != null) {
sendLoginDenied(ctx, bungee.getTranslation("already_connected_proxy", new Object[0])) sendLoginDenied(ctx, bungee.getTranslation("already_connected_proxy")).addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return; return;
} }
clientLoginState = HandshakePacketTypes.STATE_CLIENT_LOGIN; clientLoginState = HandshakePacketTypes.STATE_CLIENT_LOGIN;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN); buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN);
buf.writeByte(clientUsername.length()); buf.writeByte(clientUsername.length());
buf.writeCharSequence(clientUsername, StandardCharsets.US_ASCII); buf.writeCharSequence(clientUsername, StandardCharsets.US_ASCII);
@ -599,18 +651,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerXBungee eaglerXBungee = EaglerXBungee.getEagler(); EaglerXBungee eaglerXBungee = EaglerXBungee.getEagler();
EaglerAuthConfig authConfig = eaglerXBungee.getConfig().getAuthConfig(); EaglerAuthConfig authConfig = eaglerXBungee.getConfig().getAuthConfig();
if(authConfig.isEnableAuthentication() && clientAuth) { if(authConfig.isEnableAuthentication()) {
if(clientAuthPassword.length == 0) { if(clientAuth && clientAuthPassword.length > 0) {
sendLoginDenied(ctx, "Client provided no authentication code")
.addListener(ChannelFutureListener.CLOSE);
return;
}else {
try {
EaglercraftHandleAuthPasswordEvent handleEvent = new EaglercraftHandleAuthPasswordEvent( EaglercraftHandleAuthPasswordEvent handleEvent = new EaglercraftHandleAuthPasswordEvent(
conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername, conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername,
authRequireEvent.getSaltingData(), clientUsername, clientUUID, clientAuthPassword, authRequireEvent.getSaltingData(), clientUsername, clientUUID,
clientAuthPassword, clientEnableCookie, clientCookieData,
authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(), authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(),
(Object) authRequireEvent.getAuthAttachment(), clientRequestedServer.toString(), (Object) authRequireEvent.getAuthAttachment(), clientRequestedServer,
(handleAuthEvent) -> { (handleAuthEvent) -> {
if(handleAuthEvent.getLoginAllowed() != EaglercraftHandleAuthPasswordEvent.AuthResponse.ALLOW) { if(handleAuthEvent.getLoginAllowed() != EaglercraftHandleAuthPasswordEvent.AuthResponse.ALLOW) {
@ -644,30 +692,88 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(!handleEvent.isAsyncContinue()) { if(!handleEvent.isAsyncContinue()) {
handleEvent.doDirectContinue(); handleEvent.doDirectContinue();
} }
}catch(Throwable t) { }else if(authRequireEvent.getEnableCookieAuth()) {
throw new EventException(t); EaglercraftHandleAuthCookieEvent handleEvent = new EaglercraftHandleAuthCookieEvent(
conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername,
clientUsername, clientUUID, clientEnableCookie, clientCookieData,
authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(),
(Object) authRequireEvent.getAuthAttachment(),
clientRequestedServer, (handleAuthEvent) -> {
EaglercraftHandleAuthCookieEvent.AuthResponse resp = handleAuthEvent.getLoginAllowed();
if(resp == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "EaglercraftHandleAuthCookieEvent was not handled")
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, no installed authentication system handled: " + handleAuthEvent.toString());
return;
}
if(resp == EaglercraftHandleAuthCookieEvent.AuthResponse.DENY) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, handleAuthEvent.getLoginDeniedMessage())
.addListener(ChannelFutureListener.CLOSE);
return;
}
clientUsername = handleAuthEvent.getProfileUsername();
clientUUID = handleAuthEvent.getProfileUUID();
String texPropOverrideValue = handleAuthEvent.getApplyTexturesPropertyValue();
if(texPropOverrideValue != null) {
String texPropOverrideSig = handleAuthEvent.getApplyTexturesPropertySignature();
texturesOverrideProperty = new Property("textures", texPropOverrideValue, texPropOverrideSig);
}
overrideEaglerToVanillaSkins = handleAuthEvent.isOverrideEaglerToVanillaSkins();
if(resp == EaglercraftHandleAuthCookieEvent.AuthResponse.ALLOW) {
continueThread.run();
return;
}
if(!clientAuth && resp == EaglercraftHandleAuthCookieEvent.AuthResponse.REQUIRE_AUTH) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED, HandshakePacketTypes.AUTHENTICATION_REQUIRED
+ " [" + getAuthMethodId(authRequireEvent.getUseAuthType()) + "] " + authRequireEvent.getAuthMessage())
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().info("[" + localAddrString + "]: Displaying authentication screen");
return;
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Failed to handle authentication!")
.addListener(ChannelFutureListener.CLOSE);
return;
}
});
eaglerXBungee.getProxy().getPluginManager().callEvent(handleEvent);
if(!handleEvent.isAsyncContinue()) {
handleEvent.doDirectContinue();
}
}else {
if(authRequireEvent.getAuthRequired() != EaglercraftIsAuthRequiredEvent.AuthResponse.SKIP) {
sendLoginDenied(ctx, "Client provided no authentication code").addListener(ChannelFutureListener.CLOSE);
return;
}else {
continueThread.run();
} }
} }
}else { }else {
continueThread.run(); continueThread.run();
} }
}else { }else {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorWrong(ctx, op, "STATE_CLIENT_VERSION") sendErrorWrong(ctx, op, "STATE_CLIENT_VERSION").addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
} }
} }
break; break;
case HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA: { case HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA: {
if(clientLoginState == HandshakePacketTypes.STATE_CLIENT_LOGIN) { if(clientLoginState == HandshakePacketTypes.STATE_CLIENT_LOGIN) {
if(clientProtocolVersion <= 3) {
if(profileData.size() > 12) { if(profileData.size() >= 12) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved") sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved")
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
} }
int strlen = buffer.readUnsignedByte(); int strlen = buffer.readUnsignedByte();
String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString(); String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedShort(); strlen = buffer.readUnsignedShort();
@ -685,7 +791,32 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
} }
}else {
int count = buffer.readUnsignedByte();
if(profileData.size() + count > 12) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
for(int i = 0; i < count; ++i) {
int strlen = buffer.readUnsignedByte();
String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedShort();
byte[] readData = new byte[strlen];
buffer.readBytes(readData);
if(!profileData.containsKey(dataType)) {
profileData.put(dataType, readData);
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_DUPLICATE_PROFILE_DATA, "Multiple profile data packets of the same type recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
}
if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long");
}
}
}else { }else {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorWrong(ctx, op, "STATE_CLIENT_LOGIN").addListener(ChannelFutureListener.CLOSE); sendErrorWrong(ctx, op, "STATE_CLIENT_LOGIN").addListener(ChannelFutureListener.CLOSE);
@ -754,17 +885,17 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final ProxiedPlayer oldName = bungee.getPlayer(usernameStr); final ProxiedPlayer oldName = bungee.getPlayer(usernameStr);
if (oldName != null) { if (oldName != null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
bungee.getTranslation("already_connected_proxy", new Object[0])) bungee.getTranslation("already_connected_proxy")).addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return; return;
} }
final ChannelWrapper ch = new EaglerChannelWrapper(ctx); final EaglerChannelWrapper ch = new EaglerChannelWrapper(ctx);
InetSocketAddress baseAddress = (InetSocketAddress)ctx.channel().remoteAddress(); InetSocketAddress baseAddress = (InetSocketAddress)ctx.channel().remoteAddress();
InetAddress addr = ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).get(); InetAddress addr = ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).get();
if(addr != null) { if(addr != null) {
baseAddress = new InetSocketAddress(addr, baseAddress.getPort()); baseAddress = new InetSocketAddress(addr, baseAddress.getPort());
ch.setRemoteAddress(baseAddress); ch.setRemoteAddress(baseAddress);
} }
EaglerUpdateConfig updateconf = EaglerXBungee.getEagler().getConfig().getUpdateConfig(); EaglerUpdateConfig updateconf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
boolean blockUpdate = updateconf.isBlockAllClientUpdates(); boolean blockUpdate = updateconf.isBlockAllClientUpdates();
ClientCertificateHolder cert = null; ClientCertificateHolder cert = null;
@ -774,9 +905,34 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerUpdateSvc.sendCertificateToPlayers(cert = EaglerUpdateSvc.tryMakeHolder(b)); EaglerUpdateSvc.sendCertificateToPlayers(cert = EaglerUpdateSvc.tryMakeHolder(b));
} }
} }
final EaglerInitialHandler initialHandler = new EaglerInitialHandler(bungee, conf, ch, gameProtocolVersion, UUID clientBrandUUID = null;
String clientBrandAsString = clientBrandString.toString();
byte[] brandUUIDBytes = profileData.get("brand_uuid_v1");
if(brandUUIDBytes != null) {
if(brandUUIDBytes.length == 16) {
ByteBuf buf = Unpooled.wrappedBuffer(brandUUIDBytes);
clientBrandUUID = new UUID(buf.readLong(), buf.readLong());
if (clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_NULL_UUID)
|| clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_PENDING_UUID)
|| clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID)) {
clientBrandUUID = null;
}
}
}else {
clientBrandUUID = EaglerXBungeeAPIHelper.makeClientBrandUUIDLegacy(clientBrandAsString);
}
Map<String,byte[]> otherProfileData = new HashMap<>();
for(Entry<String,byte[]> etr2 : profileData.entrySet()) {
String str = etr2.getKey();
if(!profileDataStandard.contains(str)) {
otherProfileData.put(str, etr2.getValue());
}
}
final EaglerInitialHandler initialHandler = new EaglerInitialHandler(bungee, conf, ch, clientProtocolVersion,
gameProtocolVersion, clientBrandAsString, clientVersionString.toString(), clientBrandUUID,
usernameStr, clientUUID, offlineUUID, baseAddress, ctx.channel().attr(EaglerPipeline.HOST).get(), usernameStr, clientUUID, offlineUUID, baseAddress, ctx.channel().attr(EaglerPipeline.HOST).get(),
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), cert); ctx.channel().attr(EaglerPipeline.ORIGIN).get(), ctx.channel().attr(EaglerPipeline.USER_AGENT).get(),
cert, clientEnableCookie, clientCookieData, otherProfileData);
if(!blockUpdate) { if(!blockUpdate) {
List<ClientCertificateHolder> set = EaglerUpdateSvc.getCertList(); List<ClientCertificateHolder> set = EaglerUpdateSvc.getCertList();
synchronized(set) { synchronized(set) {
@ -794,10 +950,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final Callback<LoginEvent> complete = (Callback<LoginEvent>) new Callback<LoginEvent>() { final Callback<LoginEvent> complete = (Callback<LoginEvent>) new Callback<LoginEvent>() {
public void done(final LoginEvent result, final Throwable error) { public void done(final LoginEvent result, final Throwable error) {
if (result.isCancelled()) { if (result.isCancelled()) {
final BaseComponent[] reason = result.getCancelReasonComponents(); final BaseComponent reason = result.getReason();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
ComponentSerializer.toString(reason != null ? reason reason != null ? reason : TextComponent.fromLegacy(bungee.getTranslation("kick_message")))
: TextComponent.fromLegacyText(bungee.getTranslation("kick_message", new Object[0]))))
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
return; return;
} }
@ -805,7 +960,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN); buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN);
ctx.writeAndFlush(new BinaryWebSocketFrame(buf)).addListener(new GenericFutureListener<Future<Void>>() { ctx.writeAndFlush(new BinaryWebSocketFrame(buf)).addListener(new GenericFutureListener<Future<Void>>() {
@ -817,6 +972,10 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final UserConnection userCon = new UserConnection(bungee, ch, usernameStr, initialHandler); final UserConnection userCon = new UserConnection(bungee, ch, usernameStr, initialHandler);
userCon.setCompressionThreshold(-1); userCon.setCompressionThreshold(-1);
initialHandler.messageProtocolController = new GameProtocolMessageController(userCon,
GamePluginMessageProtocol.getByVersion(clientProtocolVersion),
GameProtocolMessageController.createServerHandler(clientProtocolVersion, userCon,
EaglerXBungee.getEagler()), conf.getDefragSendDelay());
try { try {
if (!userCon.init()) { if (!userCon.init()) {
userCon.disconnect(bungee.getTranslation("already_connected_proxy")); userCon.disconnect(bungee.getTranslation("already_connected_proxy"));
@ -845,19 +1004,18 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
pp.addBefore("HandlerBoss", "ReadTimeoutHandler", new ReadTimeoutHandler((BungeeCord.getInstance()).config.getTimeout(), TimeUnit.MILLISECONDS)); pp.addBefore("HandlerBoss", "ReadTimeoutHandler", new ReadTimeoutHandler((BungeeCord.getInstance()).config.getTimeout(), TimeUnit.MILLISECONDS));
pp.addBefore("HandlerBoss", "EaglerMinecraftDecoder", new EaglerMinecraftDecoder( pp.addBefore("HandlerBoss", "EaglerMinecraftDecoder", new EaglerMinecraftDecoder(Protocol.GAME, false, gameProtocolVersion));
EaglerBungeeProtocol.GAME, false, gameProtocolVersion));
pp.addBefore("HandlerBoss", "EaglerMinecraftByteBufEncoder", new EaglerMinecraftByteBufEncoder()); pp.addBefore("HandlerBoss", "EaglerMinecraftByteBufEncoder", new EaglerMinecraftByteBufEncoder());
pp.addBefore("HandlerBoss", "EaglerMinecraftWrappedEncoder", new EaglerMinecraftWrappedEncoder()); pp.addBefore("HandlerBoss", "EaglerMinecraftWrappedEncoder", new EaglerMinecraftWrappedEncoder());
pp.addBefore("HandlerBoss", "EaglerMinecraftEncoder", new EaglerMinecraftEncoder( pp.addBefore("HandlerBoss", "EaglerMinecraftEncoder", new EaglerMinecraftEncoder(Protocol.GAME, true, gameProtocolVersion));
EaglerBungeeProtocol.GAME, true, gameProtocolVersion));
boolean doRegisterSkins = true; boolean doRegisterSkins = true;
boolean doForceSkins = false;
EaglercraftRegisterSkinEvent registerSkinEvent = new EaglercraftRegisterSkinEvent(usernameStr, clientUUID); EaglercraftRegisterSkinEvent registerSkinEvent = new EaglercraftRegisterSkinEvent(usernameStr, clientUUID, authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null);
bungee.getPluginManager().callEvent(registerSkinEvent); bungee.getPluginManager().callEvent(registerSkinEvent);
@ -866,20 +1024,20 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(prop != null) { if(prop != null) {
texturesOverrideProperty = prop; texturesOverrideProperty = prop;
overrideEaglerToVanillaSkins = true; overrideEaglerToVanillaSkins = true;
if(clientProtocolVersion >= 4 && (EaglerXBungee.getEagler().getSkinService() instanceof SkinService)) {
doForceSkins = true;
}
}else { }else {
if(useExistingProp) { if(useExistingProp) {
overrideEaglerToVanillaSkins = true; overrideEaglerToVanillaSkins = true;
}else { }else {
byte[] custom = registerSkinEvent.getForceSetUseCustomPacket(); byte[] custom = registerSkinEvent.getForceSetUseCustomPacket();
if(custom != null) { if(custom != null) {
profileData.remove("skin_v2");
profileData.put("skin_v1", custom); profileData.put("skin_v1", custom);
overrideEaglerToVanillaSkins = false; overrideEaglerToVanillaSkins = false;
}else { if(clientProtocolVersion >= 4) {
String customUrl = registerSkinEvent.getForceSetUseURL(); doForceSkins = true;
if(customUrl != null) {
EaglerXBungee.getEagler().getSkinService().registerTextureToPlayerAssociation(customUrl, initialHandler.getUniqueId());
doRegisterSkins = false;
overrideEaglerToVanillaSkins = false;
} }
} }
} }
@ -887,14 +1045,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerBungeeConfig eaglerConf = EaglerXBungee.getEagler().getConfig(); EaglerBungeeConfig eaglerConf = EaglerXBungee.getEagler().getConfig();
if(texturesOverrideProperty != null) { if(texturesOverrideProperty != null) {
LoginResult oldProfile = initialHandler.getLoginProfile(); LoginResult oldProfile = initialHandler.getLoginProfile();
if(oldProfile == null) { if(oldProfile == null) {
oldProfile = new LoginResult(initialHandler.getUniqueId().toString(), initialHandler.getName(), null); oldProfile = new LoginResult(initialHandler.getUniqueId().toString(), initialHandler.getName(), null);
initialHandler.setLoginProfile(oldProfile); initialHandler.setLoginProfile(oldProfile);
} }
oldProfile.setProperties(new Property[] { texturesOverrideProperty, EaglerBungeeConfig.isEaglerProperty }); oldProfile.setProperties(eaglerConf.getEnableIsEaglerPlayerProperty() ? new Property[] { texturesOverrideProperty, EaglerBungeeConfig.isEaglerProperty } : new Property[] { texturesOverrideProperty });
}else { }else {
if(!useExistingProp) { if(!useExistingProp) {
String vanillaSkin = eaglerConf.getEaglerPlayersVanillaSkin(); String vanillaSkin = eaglerConf.getEaglerPlayersVanillaSkin();
@ -928,6 +1085,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
} }
doRegisterSkins = false; doRegisterSkins = false;
if(clientProtocolVersion >= 4) {
doForceSkins = true;
}
}catch(Throwable t) { }catch(Throwable t) {
} }
break; break;
@ -938,10 +1098,18 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
if(doRegisterSkins) { if(doRegisterSkins) {
if(profileData.containsKey("skin_v1")) { if(clientProtocolVersion >= 4 && profileData.containsKey("skin_v2")) {
try {
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v2"),
EaglerXBungee.getEagler().getSkinService(), 4);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString());
}
}else if(profileData.containsKey("skin_v1")) {
try { try {
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v1"), SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v1"),
EaglerXBungee.getEagler().getSkinService()); EaglerXBungee.getEagler().getSkinService(), 3);
} catch (Throwable ex) { } catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService()); SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString()); EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString());
@ -950,8 +1118,11 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService()); SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
} }
} }
if(doForceSkins) {
EaglerXBungee.getEagler().getSkinService().processForceSkin(clientUUID, initialHandler);
}
EaglercraftRegisterCapeEvent registerCapeEvent = new EaglercraftRegisterCapeEvent(usernameStr, clientUUID); EaglercraftRegisterCapeEvent registerCapeEvent = new EaglercraftRegisterCapeEvent(usernameStr, clientUUID, authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null);
bungee.getPluginManager().callEvent(registerCapeEvent); bungee.getPluginManager().callEvent(registerCapeEvent);
@ -971,11 +1142,21 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}else { }else {
CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getCapeService()); CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getCapeService());
} }
if(forceCape != null && clientProtocolVersion >= 4) {
EaglerXBungee.getEagler().getCapeService().processForceCape(clientUUID, initialHandler);
}
if(conf.getEnableVoiceChat()) { if(conf.getEnableVoiceChat()) {
EaglerXBungee.getEagler().getVoiceService().handlePlayerLoggedIn(userCon); EaglerXBungee.getEagler().getVoiceService().handlePlayerLoggedIn(userCon);
} }
if(clientProtocolVersion >= 4) {
GameMessagePacket pauseMenuPkt = EaglerXBungee.getEagler().getConfig().getPauseMenuConf().getPacket();
if(pauseMenuPkt != null) {
initialHandler.sendEaglerMessage(pauseMenuPkt);
}
}
ServerInfo server; ServerInfo server;
if (bungee.getReconnectHandler() != null) { if (bungee.getReconnectHandler() != null) {
server = bungee.getReconnectHandler().getServer((ProxiedPlayer) userCon); server = bungee.getReconnectHandler().getServer((ProxiedPlayer) userCon);
@ -1015,10 +1196,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final Callback<PreLoginEvent> completePre = new Callback<PreLoginEvent>() { final Callback<PreLoginEvent> completePre = new Callback<PreLoginEvent>() {
public void done(PreLoginEvent var1, Throwable var2) { public void done(PreLoginEvent var1, Throwable var2) {
if (var1.isCancelled()) { if (var1.isCancelled()) {
final BaseComponent[] reason = var1.getCancelReasonComponents(); final BaseComponent reason = var1.getReason();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
ComponentSerializer.toString(reason != null ? reason reason != null ? reason : TextComponent.fromLegacy(bungee.getTranslation("kick_message")))
: TextComponent.fromLegacyText(bungee.getTranslation("kick_message", new Object[0]))))
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
}else { }else {
bungee.getPluginManager().callEvent(new LoginEvent(initialHandler, complete)); bungee.getPluginManager().callEvent(new LoginEvent(initialHandler, complete));
@ -1109,6 +1289,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(!handler.isClosed() && !handler.shouldKeepAlive()) { if(!handler.isClosed() && !handler.shouldKeepAlive()) {
connectionClosed = true; connectionClosed = true;
handler.close(); handler.close();
}else {
ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get().queryHandler = handler;
} }
}else { }else {
connectionClosed = true; connectionClosed = true;
@ -1121,7 +1303,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
} }
} }
private int getAuthMethodId(AuthMethod meth) { private int getAuthMethodId(EaglercraftIsAuthRequiredEvent.AuthMethod meth) {
switch(meth) { switch(meth) {
case PLAINTEXT: case PLAINTEXT:
return 255; // plaintext authentication return 255; // plaintext authentication
@ -1136,13 +1318,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private ChannelFuture sendLoginDenied(ChannelHandlerContext ctx, String reason) { private ChannelFuture sendLoginDenied(ChannelHandlerContext ctx, String reason) {
if((!isProtocolExchanged || clientProtocolVersion == 2) && reason.length() > 255) { if((!isProtocolExchanged || clientProtocolVersion == 2) && reason.length() > 255) {
reason = reason.substring(0, 256); reason = reason.substring(0, 255);
}else if(reason.length() > 65535) { }else if(reason.length() > 65535) {
reason = reason.substring(0, 65536); reason = reason.substring(0, 65535);
} }
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true; connectionClosed = true;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN); buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN);
byte[] msg = reason.getBytes(StandardCharsets.UTF_8); byte[] msg = reason.getBytes(StandardCharsets.UTF_8);
if(!isProtocolExchanged || clientProtocolVersion == 2) { if(!isProtocolExchanged || clientProtocolVersion == 2) {
@ -1158,15 +1340,23 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_WRONG_PACKET, "Wrong Packet #" + op + " in state '" + state + "'"); return sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_WRONG_PACKET, "Wrong Packet #" + op + " in state '" + state + "'");
} }
private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, BaseComponent comp) {
if((!isProtocolExchanged || clientProtocolVersion == 2)) {
return sendErrorCode(ctx, code, ComponentSerializer.toString(comp));
}else {
return sendErrorCode(ctx, code, comp.toLegacyText());
}
}
private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, String str) { private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, String str) {
if((!isProtocolExchanged || clientProtocolVersion == 2) && str.length() > 255) { if((!isProtocolExchanged || clientProtocolVersion == 2) && str.length() > 255) {
str = str.substring(0, 256); str = str.substring(0, 255);
}else if(str.length() > 65535) { }else if(str.length() > 65535) {
str = str.substring(0, 65536); str = str.substring(0, 65535);
} }
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE; clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true; connectionClosed = true;
ByteBuf buf = Unpooled.buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ERROR); buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ERROR);
buf.writeByte(code); buf.writeByte(code);
byte[] msg = str.getBytes(StandardCharsets.UTF_8); byte[] msg = str.getBytes(StandardCharsets.UTF_8);

View File

@ -0,0 +1,295 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCPacket;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.WrongRPCPacketException;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.CPacketRPCEnabled;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.CPacketRPCSubscribeEvents;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEnabledFailure;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEnabledSuccess;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewOpenClose;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Server;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class BackendRPCSessionHandler {
public static BackendRPCSessionHandler createForPlayer(EaglerInitialHandler eaglerHandler) {
return new BackendRPCSessionHandler(eaglerHandler);
}
protected final EaglerInitialHandler eaglerHandler;
private Server currentServer = null;
private EaglerBackendRPCProtocol currentProtocol = null;
private EaglerBackendRPCHandler currentHandler = null;
private int subscribedEvents = 0;
private final AtomicInteger currentVoiceState = new AtomicInteger(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
private final ReentrantLock inputStreamLock = new ReentrantLock();
private final ReentrantLock outputStreamLock = new ReentrantLock();
private final ReusableByteArrayInputStream reusableInputStream = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream reusableOutputStream = new ReusableByteArrayOutputStream();
private final DataInputStream dataInputStream = new DataInputStream(reusableInputStream);
private final DataOutputStream dataOutputStream = new DataOutputStream(reusableOutputStream);
private BackendRPCSessionHandler(EaglerInitialHandler eaglerHandler) {
this.eaglerHandler = eaglerHandler;
}
public void handleRPCPacket(Server server, byte[] data) {
synchronized(this) {
if(currentServer != null) {
if(currentServer != server) {
return;
}
}else {
handleCreateContext(server, data);
return;
}
}
EaglerBackendRPCPacket packet;
try {
packet = decodeRPCPacket(currentProtocol, data);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + eaglerHandler.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol packet for user \"" + eaglerHandler.getName() + "\"", e);
return;
}
packet.handlePacket(currentHandler);
}
protected EaglerBackendRPCPacket decodeRPCPacket(EaglerBackendRPCProtocol protocol, byte[] data) throws IOException {
EaglerBackendRPCPacket ret;
if(inputStreamLock.tryLock()) {
try {
reusableInputStream.feedBuffer(data);
ret = protocol.readPacket(dataInputStream, EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
}finally {
inputStreamLock.unlock();
}
}else {
ReusableByteArrayInputStream bai = new ReusableByteArrayInputStream();
bai.feedBuffer(data);
ret = protocol.readPacket(new DataInputStream(bai), EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
}
return ret;
}
public void sendRPCPacket(EaglerBackendRPCPacket packet) {
if(currentServer != null) {
sendRPCPacket(currentProtocol, currentServer, packet);
}else {
EaglerXBungee.logger()
.warning("[" + eaglerHandler.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + eaglerHandler.getName()
+ "\", the RPC connection is not initialized!");
}
}
protected void sendRPCPacket(EaglerBackendRPCProtocol protocol, Server server, EaglerBackendRPCPacket packet) {
byte[] ret;
int len = packet.length() + 1;
if(outputStreamLock.tryLock()) {
try {
reusableOutputStream.feedBuffer(new byte[len > 0 ? len : 64]);
try {
protocol.writePacket(dataOutputStream, EaglerBackendRPCProtocol.SERVER_TO_CLIENT, packet);
}catch(IOException ex) {
throw new IllegalStateException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
ret = reusableOutputStream.returnBuffer();
}finally {
outputStreamLock.unlock();
}
}else {
ReusableByteArrayOutputStream bao = new ReusableByteArrayOutputStream();
bao.feedBuffer(new byte[len > 0 ? len : 64]);
try {
protocol.writePacket(new DataOutputStream(bao), EaglerBackendRPCProtocol.SERVER_TO_CLIENT, packet);
}catch(IOException ex) {
throw new IllegalStateException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
ret = bao.returnBuffer();
}
if(len > 0 && len != ret.length) {
EaglerXBungee.logger()
.warning("[" + eaglerHandler.getSocketAddress() + "]: Backend RPC packet type "
+ packet.getClass().getSimpleName() + " was the wrong length for user \""
+ eaglerHandler.getName() + "\" after serialization: " + ret.length + " != " + len);
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, ret);
}
public void handleConnectionLost(ServerInfo server) {
if(currentServer != null) {
handleDestroyContext();
}
}
private void handleDestroyContext() {
currentServer = null;
currentProtocol = null;
currentHandler = null;
subscribedEvents = 0;
}
private void handleCreateContext(Server server, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = decodeRPCPacket(EaglerBackendRPCProtocol.INIT, data);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + eaglerHandler.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol handshake for user \"" + eaglerHandler.getName() + "\"", e);
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!ArrayUtils.contains(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress()
+ "]: Unsupported backend RPC protocol version for user \"" + eaglerHandler.getName() + "\"");
sendRPCPacket(EaglerBackendRPCProtocol.INIT, server, new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_OUTDATED_SERVER));
return;
}
sendRPCPacket(EaglerBackendRPCProtocol.INIT, server, new SPacketRPCEnabledSuccess(EaglerBackendRPCProtocol.V1.vers, eaglerHandler.getEaglerProtocolHandshake()));
currentServer = server;
currentProtocol = EaglerBackendRPCProtocol.V1;
currentHandler = new ServerV1RPCProtocolHandler(this, server, eaglerHandler);
}
public static void handlePacketOnVanilla(Server server, UserConnection player, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = EaglerBackendRPCProtocol.INIT.readPacket(new DataInputStream(new ByteArrayInputStream(data)), EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol handshake for user \"" + player.getName() + "\"", e);
EaglerXBungee.logger().severe("(Note: this player is not using Eaglercraft!)");
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!ArrayUtils.contains(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXBungee.logger().severe("[" + player.getSocketAddress()
+ "]: Unsupported backend RPC protocol version for user \"" + player.getName() + "\"");
ByteArrayOutputStream bao = new ByteArrayOutputStream();
try {
EaglerBackendRPCProtocol.INIT.writePacket(new DataOutputStream(bao),
EaglerBackendRPCProtocol.SERVER_TO_CLIENT,
new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_OUTDATED_SERVER));
}catch(IOException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + player.getName() + "\"", ex);
EaglerXBungee.logger().severe("(Note: this player is not using Eaglercraft!)");
return;
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, bao.toByteArray());
return;
}
EaglerXBungee.logger().warning("[" + player.getSocketAddress()
+ "]: Tried to open backend RPC protocol connection for non-eagler player \"" + player.getName() + "\"");
ByteArrayOutputStream bao = new ByteArrayOutputStream();
try {
EaglerBackendRPCProtocol.INIT.writePacket(new DataOutputStream(bao),
EaglerBackendRPCProtocol.SERVER_TO_CLIENT,
new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_NOT_EAGLER_PLAYER));
}catch(IOException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + player.getName() + "\"", ex);
return;
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, bao.toByteArray());
}
public void setSubscribedEvents(int eventsToEnable) {
int oldSubscribedEvents = subscribedEvents;
subscribedEvents = eventsToEnable;
if ((oldSubscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) == 0
&& (eventsToEnable & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0) {
currentVoiceState.set(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
VoiceService svc = EaglerXBungee.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState state = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), currentServer.getInfo());
if(state == EnumVoiceState.DISABLED) {
handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}else if(state == EnumVoiceState.ENABLED) {
handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_ENABLED);
}
}
}
if ((oldSubscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) == 0
&& (eventsToEnable & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) != 0) {
if(eaglerHandler.webViewMessageChannelOpen.get()) {
sendRPCPacket(new SPacketRPCEventWebViewOpenClose(true, eaglerHandler.webViewMessageChannelName));
}
}
if ((eventsToEnable & ~(CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE
| CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_MESSAGE
| CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE)) != 0) {
EaglerXBungee.logger()
.severe("[" + eaglerHandler.getSocketAddress() + "]: Unsupported events were subscribed to for \""
+ eaglerHandler.getName() + "\" via backend RPC protocol, bitfield: " + subscribedEvents);
}
}
public boolean isSubscribed(EnumSubscribedEvent eventType) {
switch(eventType) {
case WEBVIEW_OPEN_CLOSE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) != 0;
case WEBVIEW_MESSAGE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_MESSAGE) != 0;
case TOGGLE_VOICE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0;
default:
return false;
}
}
public void handleDisabled() {
handleDestroyContext();
}
public void handleVoiceStateTransition(int voiceState) {
if((subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0) {
int oldState = currentVoiceState.getAndSet(voiceState);
if(oldState != voiceState) {
sendRPCPacket(new SPacketRPCEventToggledVoice(oldState, voiceState));
}
}
}
}

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public enum EnumSubscribedEvent {
WEBVIEW_OPEN_CLOSE,
WEBVIEW_MESSAGE,
TOGGLE_VOICE;
}

View File

@ -0,0 +1,435 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.RandomAccess;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeHideV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsRegisterV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsReleaseV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.api.connection.Server;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ServerV1RPCProtocolHandler implements EaglerBackendRPCHandler {
protected final BackendRPCSessionHandler sessionHandler;
protected final Server server;
protected final EaglerInitialHandler eaglerHandler;
public ServerV1RPCProtocolHandler(BackendRPCSessionHandler sessionHandler, Server server, EaglerInitialHandler eaglerHandler) {
this.sessionHandler = sessionHandler;
this.server = server;
this.eaglerHandler = eaglerHandler;
}
public void handleClient(CPacketRPCRequestPlayerInfo packet) {
switch(packet.requestType) {
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_REAL_UUID: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeUUID(packet.requestID, eaglerHandler.getUniqueId()));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_REAL_IP: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, ((InetSocketAddress)eaglerHandler.getSocketAddress()).getAddress().getHostAddress()));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_ORIGIN: {
String origin = eaglerHandler.getOrigin();
if(origin != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, origin));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_USER_AGENT: {
String userAgent = eaglerHandler.getUserAgent();
if(userAgent != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, userAgent));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_SKIN_DATA: {
SkinPacketVersionCache skinData = EaglerXBungee.getEagler().getSkinService().getSkin(eaglerHandler.getUniqueId());
if(skinData != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeBytes(packet.requestID, skinData.getV3HandshakeData()));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CAPE_DATA: {
byte[] capeData = EaglerXBungee.getEagler().getCapeService().getCapeHandshakeData(eaglerHandler.getUniqueId());
if(capeData != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeBytes(packet.requestID, capeData));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_COOKIE: {
boolean cookieEnabled = eaglerHandler.getCookieAllowed();
byte[] cookieData = cookieEnabled ? eaglerHandler.getCookieData() : null;
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeCookie(packet.requestID, cookieEnabled, cookieData));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_STR: {
String clientBrandStr = eaglerHandler.getEaglerBrandString();
if(clientBrandStr != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, clientBrandStr));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_VERSION_STR: {
String clientVersionStr = eaglerHandler.getEaglerVersionString();
if(clientVersionStr != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, clientVersionStr));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_VERSION_STR: {
String clientBrandStr = eaglerHandler.getEaglerBrandString();
String clientVersionStr = eaglerHandler.getEaglerVersionString();
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, "" + clientBrandStr + " " + clientVersionStr));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_UUID: {
UUID brandUUID = eaglerHandler.getClientBrandUUID();
if(brandUUID != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeUUID(packet.requestID, brandUUID));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_VOICE_STATUS: {
int voiceState;
VoiceService svc = EaglerXBungee.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState enumVoiceState = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), server.getInfo());
switch(enumVoiceState) {
case SERVER_DISABLE:
default:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_SERVER_DISABLE;
break;
case DISABLED:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_DISABLED;
break;
case ENABLED:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_ENABLED;
break;
}
}else {
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_SERVER_DISABLE;
}
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeVoiceStatus(packet.requestID, voiceState));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_WEBVIEW_STATUS: {
EnumWebViewState enumWebViewState = eaglerHandler.getWebViewState();
int webViewStatus;
String webViewChannel;
switch(enumWebViewState) {
case NOT_SUPPORTED:
default:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_NOT_SUPPORTED;
webViewChannel = null;
break;
case SERVER_DISABLE:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_SERVER_DISABLE;
webViewChannel = null;
break;
case CHANNEL_CLOSED:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_CHANNEL_CLOSED;
webViewChannel = null;
break;
case CHANNEL_OPEN:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_CHANNEL_OPEN;
webViewChannel = eaglerHandler.getWebViewMessageChannelName();
break;
}
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeWebViewStatus(packet.requestID, webViewStatus, webViewChannel));
}
break;
default: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeError(packet.requestID, "Unknown request type: " + packet.requestType));
}
break;
}
}
public void handleClient(CPacketRPCSubscribeEvents packet) {
sessionHandler.setSubscribedEvents(packet.eventsToEnable);
}
public void handleClient(CPacketRPCSetPlayerSkin packet) {
try {
byte[] bs = packet.skinPacket;
if(bs.length < 5) {
throw new IOException();
}
if(bs[0] == (byte)1) {
if(bs.length != 5) {
throw new IOException();
}
EaglerXBungeeAPIHelper.changePlayerSkinPreset(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
(bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF), packet.notifyOthers);
}else if(bs[0] == (byte)2) {
if(bs.length < 2) {
throw new IOException();
}
byte[] cust = new byte[bs.length - 2];
System.arraycopy(bs, 2, cust, 0, cust.length);
EaglerXBungeeAPIHelper.changePlayerSkinCustom(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
bs[1] & 0xFF, cust, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress() + "]: Invalid CPacketRPCSetPlayerSkin packet recieved for player \""
+ eaglerHandler.getName() + "\" from backend RPC protocol!");
return;
}
}
public void handleClient(CPacketRPCSetPlayerCape packet) {
try {
byte[] bs = packet.capePacket;
if(bs.length < 5) {
throw new IOException();
}
if(bs[0] == (byte)1) {
if(bs.length != 5) {
throw new IOException();
}
EaglerXBungeeAPIHelper.changePlayerCapePreset(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
(bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF), packet.notifyOthers);
}else if(bs[0] == (byte)2) {
if(bs.length != 1174) {
throw new IOException();
}
byte[] cust = new byte[bs.length - 1];
System.arraycopy(bs, 1, cust, 0, cust.length);
EaglerXBungeeAPIHelper.changePlayerCapeCustom(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
cust, true, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress() + "]: Invalid CPacketRPCSetPlayerCape packet recieved for player \""
+ eaglerHandler.getName() + "\" from backend RPC protocol!");
return;
}
}
public void handleClient(CPacketRPCSetPlayerCookie packet) {
eaglerHandler.setCookieData(packet.cookieData, packet.expires, packet.revokeQuerySupported, packet.saveToDisk);
}
public void handleClient(CPacketRPCSetPlayerFNAWEn packet) {
EaglerXBungeeAPIHelper.setEnableForceFNAWSkins(eaglerHandler, packet.enable, packet.force);
}
public void handleClient(CPacketRPCRedirectPlayer packet) {
eaglerHandler.redirectPlayerToWebSocket(packet.redirectURI);
}
public void handleClient(CPacketRPCResetPlayerMulti packet) {
EaglerXBungeeAPIHelper.resetPlayerMulti(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler), packet.resetSkin,
packet.resetCape, packet.resetFNAWForce, packet.notifyOtherPlayers);
}
public void handleClient(CPacketRPCSendWebViewMessage packet) {
eaglerHandler.sendWebViewMessage(packet.messageType, packet.messageContent);
}
public void handleClient(CPacketRPCSetPauseMenuCustom packet) {
if(eaglerHandler.getEaglerProtocol().ver >= 4) {
EaglerPauseMenuConfig defaultConf = EaglerXBungee.getEagler().getConfig().getPauseMenuConf();
SPacketCustomizePauseMenuV4EAG defaultPacket = defaultConf.getPacket();
int serverInfoMode = packet.serverInfoMode;
String serverInfoButtonText;
String serverInfoURL;
byte[] serverInfoHash;
int serverInfoEmbedPerms;
String serverInfoEmbedTitle;
if(serverInfoMode == CPacketRPCSetPauseMenuCustom.SERVER_INFO_MODE_INHERIT_DEFAULT) {
serverInfoMode = defaultPacket.serverInfoMode;
serverInfoButtonText = defaultPacket.serverInfoButtonText;
serverInfoURL = defaultPacket.serverInfoURL;
serverInfoHash = defaultPacket.serverInfoHash;
serverInfoEmbedPerms = defaultPacket.serverInfoEmbedPerms;
serverInfoEmbedTitle = defaultPacket.serverInfoEmbedTitle;
}else {
serverInfoButtonText = packet.serverInfoButtonText;
serverInfoURL = packet.serverInfoURL;
serverInfoHash = packet.serverInfoHash;
serverInfoEmbedPerms = packet.serverInfoEmbedPerms;
serverInfoEmbedTitle = packet.serverInfoEmbedTitle;
}
int discordButtonMode = packet.discordButtonMode;
String discordButtonText;
String discordInviteURL;
if(discordButtonMode == CPacketRPCSetPauseMenuCustom.DISCORD_MODE_INHERIT_DEFAULT) {
discordButtonMode = defaultPacket.discordButtonMode;
discordButtonText = defaultPacket.discordButtonText;
discordInviteURL = defaultPacket.discordInviteURL;
}else {
discordButtonText = packet.discordButtonText;
discordInviteURL = packet.discordInviteURL;
}
Map<String, Integer> imageMappings = packet.imageMappings;
List<PacketImageData> imageData = packet.imageData;
List<net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData> imageDataConv = imageData != null
? new ArrayList<>(imageData.size()) : null;
if(imageDataConv != null) {
for(int i = 0, l = imageData.size(); i < l; ++i) {
PacketImageData etr = imageData.get(i);
imageDataConv.add(new net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData
(etr.width, etr.height, etr.rgba));
}
}
eaglerHandler.sendEaglerMessage(new SPacketCustomizePauseMenuV4EAG(serverInfoMode, serverInfoButtonText,
serverInfoURL, serverInfoHash, serverInfoEmbedPerms, serverInfoEmbedTitle, discordButtonMode,
discordButtonText, discordInviteURL, imageMappings, imageDataConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCSetPauseMenuCustom for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support pause menu customization!");
}
}
public void handleClient(CPacketRPCNotifIconRegister packet) {
if(eaglerHandler.notificationSupported()) {
List<SPacketNotifIconsRegisterV4EAG.CreateIcon> createIconsConv = new ArrayList<>(packet.notifIcons.size());
for(Entry<UUID,PacketImageData> etr : packet.notifIcons.entrySet()) {
UUID uuid = etr.getKey();
PacketImageData imgData = etr.getValue();
createIconsConv.add(new SPacketNotifIconsRegisterV4EAG.CreateIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), new net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData
(imgData.width, imgData.height, imgData.rgba)));
}
eaglerHandler.sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(createIconsConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifIconRegister for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifIconRelease packet) {
if(eaglerHandler.notificationSupported()) {
List<SPacketNotifIconsReleaseV4EAG.DestroyIcon> destroyIconsConv = new ArrayList<>(packet.iconsToRelease.size());
if(packet.iconsToRelease instanceof RandomAccess) {
List<UUID> lst = (List<UUID>)packet.iconsToRelease;
for(int i = 0, l = lst.size(); i < l; ++i) {
UUID uuid = lst.get(i);
destroyIconsConv.add(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()));
}
}else {
for(UUID uuid : packet.iconsToRelease) {
destroyIconsConv.add(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()));
}
}
eaglerHandler.sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(destroyIconsConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifIconRelease for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifBadgeShow packet) {
if(eaglerHandler.notificationSupported()) {
SPacketNotifBadgeShowV4EAG.EnumBadgePriority translatedEnum;
switch(packet.priority) {
case LOW:
default:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.LOW;
break;
case NORMAL:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.NORMAL;
break;
case HIGHER:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.HIGHER;
break;
case HIGHEST:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.HIGHEST;
break;
}
eaglerHandler.sendEaglerMessage(new SPacketNotifBadgeShowV4EAG(packet.badgeUUID.getMostSignificantBits(),
packet.badgeUUID.getLeastSignificantBits(), packet.bodyComponent, packet.titleComponent,
packet.sourceComponent, packet.originalTimestampSec, packet.silent, translatedEnum,
(packet.mainIconUUID != null ? packet.mainIconUUID.getMostSignificantBits() : 0l),
(packet.mainIconUUID != null ? packet.mainIconUUID.getLeastSignificantBits() : 0l),
(packet.titleIconUUID != null ? packet.titleIconUUID.getMostSignificantBits() : 0l),
(packet.titleIconUUID != null ? packet.titleIconUUID.getLeastSignificantBits() : 0l),
packet.hideAfterSec, packet.expireAfterSec, packet.backgroundColor, packet.bodyTxtColor,
packet.titleTxtColor, packet.sourceTxtColor));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifBadgeShow for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifBadgeHide packet) {
if(eaglerHandler.notificationSupported()) {
eaglerHandler.sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(packet.badgeUUID.getMostSignificantBits(),
packet.badgeUUID.getLeastSignificantBits()));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifBadgeHide for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCDisabled packet) {
sessionHandler.handleDisabled();
}
public void handleClient(CPacketRPCSendRawMessage packet) {
eaglerHandler.getEaglerMessageController().getUserConnection().sendData(packet.messageChannel, packet.messageData);
}
}

View File

@ -1,254 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import net.md_5.bungee.protocol.BadPacketException;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.*;
import java.util.function.Supplier;
/**
* The original net.md_5.bungee.protocol.Protocol is inaccessible due to java
* security rules
*/
public enum EaglerBungeeProtocol {
// Undef
HANDSHAKE {
{
TO_SERVER.registerPacket(Handshake.class, Handshake::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
}
},
// 0
GAME {
{
TO_CLIENT.registerPacket(KeepAlive.class, KeepAlive::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(Login.class, Login::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_CLIENT.registerPacket(Chat.class, Chat::new, map(ProtocolConstants.MINECRAFT_1_8, 0x02));
TO_CLIENT.registerPacket(Respawn.class, Respawn::new, map(ProtocolConstants.MINECRAFT_1_8, 0x07));
TO_CLIENT.registerPacket(PlayerListItem.class, // PlayerInfo
PlayerListItem::new, map(ProtocolConstants.MINECRAFT_1_8, 0x38));
TO_CLIENT.registerPacket(TabCompleteResponse.class, TabCompleteResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3A));
TO_CLIENT.registerPacket(ScoreboardObjective.class, ScoreboardObjective::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3B));
TO_CLIENT.registerPacket(ScoreboardScore.class, ScoreboardScore::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3C));
TO_CLIENT.registerPacket(ScoreboardDisplay.class, ScoreboardDisplay::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3D));
TO_CLIENT.registerPacket(Team.class, Team::new, map(ProtocolConstants.MINECRAFT_1_8, 0x3E));
TO_CLIENT.registerPacket(PluginMessage.class, PluginMessage::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3F));
TO_CLIENT.registerPacket(Kick.class, Kick::new, map(ProtocolConstants.MINECRAFT_1_8, 0x40));
TO_CLIENT.registerPacket(Title.class, Title::new, map(ProtocolConstants.MINECRAFT_1_8, 0x45));
TO_CLIENT.registerPacket(PlayerListHeaderFooter.class, PlayerListHeaderFooter::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x47));
TO_CLIENT.registerPacket(EntityStatus.class, EntityStatus::new, map(ProtocolConstants.MINECRAFT_1_8, 0x1A));
TO_SERVER.registerPacket(KeepAlive.class, KeepAlive::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(Chat.class, Chat::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_SERVER.registerPacket(TabCompleteRequest.class, TabCompleteRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x14));
TO_SERVER.registerPacket(ClientSettings.class, ClientSettings::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x15));
TO_SERVER.registerPacket(PluginMessage.class, PluginMessage::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x17));
}
},
// 1
STATUS {
{
TO_CLIENT.registerPacket(StatusResponse.class, StatusResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(PingPacket.class, PingPacket::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_SERVER.registerPacket(StatusRequest.class, StatusRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(PingPacket.class, PingPacket::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
}
},
// 2
LOGIN {
{
TO_CLIENT.registerPacket(Kick.class, Kick::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(EncryptionRequest.class, EncryptionRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_CLIENT.registerPacket(LoginSuccess.class, LoginSuccess::new, map(ProtocolConstants.MINECRAFT_1_8, 0x02));
TO_CLIENT.registerPacket(SetCompression.class, SetCompression::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x03));
TO_SERVER.registerPacket(LoginRequest.class, LoginRequest::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(EncryptionResponse.class, EncryptionResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x01));
}
},
// 3
CONFIGURATION {
};
/* ======================================================================== */
public static final int MAX_PACKET_ID = 0xFF;
/* ======================================================================== */
public final DirectionData TO_SERVER = new DirectionData(this, ProtocolConstants.Direction.TO_SERVER);
public final DirectionData TO_CLIENT = new DirectionData(this, ProtocolConstants.Direction.TO_CLIENT);
public static void main(String[] args) {
for (int version : ProtocolConstants.SUPPORTED_VERSION_IDS) {
dump(version);
}
}
private static void dump(int version) {
for (EaglerBungeeProtocol protocol : EaglerBungeeProtocol.values()) {
dump(version, protocol);
}
}
private static void dump(int version, EaglerBungeeProtocol protocol) {
dump(version, protocol.TO_CLIENT);
dump(version, protocol.TO_SERVER);
}
private static void dump(int version, DirectionData data) {
for (int id = 0; id < MAX_PACKET_ID; id++) {
DefinedPacket packet = data.createPacket(id, version);
if (packet != null) {
System.out.println(version + " " + data.protocolPhase + " " + data.direction + " " + id + " "
+ packet.getClass().getSimpleName());
}
}
}
private static class ProtocolData {
private final int protocolVersion;
private final TObjectIntMap<Class<? extends DefinedPacket>> packetMap = new TObjectIntHashMap<>(MAX_PACKET_ID);
@SuppressWarnings("unchecked")
private final Supplier<? extends DefinedPacket>[] packetConstructors = new Supplier[MAX_PACKET_ID];
private ProtocolData(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
}
private static class ProtocolMapping {
private final int protocolVersion;
private final int packetID;
private ProtocolMapping(int protocolVersion, int packetID) {
this.protocolVersion = protocolVersion;
this.packetID = packetID;
}
}
// Helper method
private static ProtocolMapping map(int protocol, int id) {
return new ProtocolMapping(protocol, id);
}
public static final class DirectionData {
private final TIntObjectMap<ProtocolData> protocols = new TIntObjectHashMap<>();
//
private final EaglerBungeeProtocol protocolPhase;
private final ProtocolConstants.Direction direction;
public DirectionData(EaglerBungeeProtocol protocolPhase, ProtocolConstants.Direction direction) {
this.protocolPhase = protocolPhase;
this.direction = direction;
for (int protocol : ProtocolConstants.SUPPORTED_VERSION_IDS) {
protocols.put(protocol, new ProtocolData(protocol));
}
}
private ProtocolData getProtocolData(int version) {
ProtocolData protocol = protocols.get(version);
if (protocol == null && (protocolPhase != EaglerBungeeProtocol.GAME)) {
protocol = Iterables.getFirst(protocols.valueCollection(), null);
}
return protocol;
}
public final DefinedPacket createPacket(int id, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version " + version);
}
if (id > MAX_PACKET_ID || id < 0) {
throw new BadPacketException("Packet with id " + id + " outside of range");
}
Supplier<? extends DefinedPacket> constructor = protocolData.packetConstructors[id];
return (constructor == null) ? null : constructor.get();
}
private void registerPacket(Class<? extends DefinedPacket> packetClass,
Supplier<? extends DefinedPacket> constructor, ProtocolMapping... mappings) {
int mappingIndex = 0;
ProtocolMapping mapping = mappings[mappingIndex];
for (int protocol : ProtocolConstants.SUPPORTED_VERSION_IDS) {
if (protocol < mapping.protocolVersion) {
// This is a new packet, skip it till we reach the next protocol
continue;
}
if (mapping.protocolVersion < protocol && mappingIndex + 1 < mappings.length) {
// Mapping is non current, but the next one may be ok
ProtocolMapping nextMapping = mappings[mappingIndex + 1];
if (nextMapping.protocolVersion == protocol) {
Preconditions.checkState(nextMapping.packetID != mapping.packetID,
"Duplicate packet mapping (%s, %s)", mapping.protocolVersion,
nextMapping.protocolVersion);
mapping = nextMapping;
mappingIndex++;
}
}
if (mapping.packetID < 0) {
break;
}
ProtocolData data = protocols.get(protocol);
data.packetMap.put(packetClass, mapping.packetID);
data.packetConstructors[mapping.packetID] = constructor;
}
}
public boolean hasPacket(Class<? extends DefinedPacket> packet, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version");
}
return protocolData.packetMap.containsKey(packet);
}
final int getId(Class<? extends DefinedPacket> packet, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version");
}
Preconditions.checkArgument(protocolData.packetMap.containsKey(packet),
"Cannot get ID for packet %s in phase %s with direction %s", packet, protocolPhase, direction);
return protocolData.packetMap.get(packet);
}
}
}

View File

@ -0,0 +1,291 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.ScheduledFuture;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleInputBufferImpl;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.DefinedPacket;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class GameProtocolMessageController {
public final GamePluginMessageProtocol protocol;
public final GameMessageHandler handler;
private final ReusableByteArrayInputStream byteInputStreamSingleton = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream byteOutputStreamSingleton = new ReusableByteArrayOutputStream();
private final SimpleInputBufferImpl inputStreamSingleton = new SimpleInputBufferImpl(byteInputStreamSingleton);
private final SimpleOutputBufferImpl outputStreamSingleton = new SimpleOutputBufferImpl(byteOutputStreamSingleton);
private final ReentrantLock inputStreamLock = new ReentrantLock();
private final ReentrantLock outputStreamLock = new ReentrantLock();
private final UserConnection owner;
private int defagSendDelay;
private final List<byte[]> sendQueueV4 = new LinkedList<>();
private volatile int sendQueueByteLengthV4 = 0;
private volatile Callable<Void> futureSendCallableV4 = null;
private volatile ScheduledFuture<Void> futureSendTaskV4 = null;
public GameProtocolMessageController(UserConnection owner, GamePluginMessageProtocol protocol,
GameMessageHandler handler, int defagSendDelay) {
this.owner = owner;
this.protocol = protocol;
this.handler = handler;
this.defagSendDelay = defagSendDelay;
}
public boolean handlePacket(String channel, byte[] data) throws IOException {
GameMessagePacket pkt;
if(inputStreamLock.tryLock()) {
try {
byteInputStreamSingleton.feedBuffer(data);
if(protocol.ver >= 4 && data.length > 0 && data[0] == (byte)0xFF && channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
inputStreamSingleton.readByte();
int count = inputStreamSingleton.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputStreamSingleton.readVarInt();
k = byteInputStreamSingleton.getReaderIndex() + j;
if(j > inputStreamSingleton.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputStreamSingleton.available());
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputStreamSingleton);
if(pkt != null) {
pkt.handlePacket(handler);
}else {
throw new IOException("Unknown packet type in fragment!");
}
if(byteInputStreamSingleton.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + byteInputStreamSingleton.getReaderIndex() - k) + " != " + j);
}
}
if(inputStreamSingleton.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputStreamSingleton.available() + " bytes)");
}
return true;
}
inputStreamSingleton.setToByteArrayReturns(data);
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputStreamSingleton);
if(pkt != null && byteInputStreamSingleton.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
}finally {
byteInputStreamSingleton.feedBuffer(null);
inputStreamSingleton.setToByteArrayReturns(null);
inputStreamLock.unlock();
}
}else {
// slow version that makes multiple new objects
ReusableByteArrayInputStream inputStream = new ReusableByteArrayInputStream();
inputStream.feedBuffer(data);
SimpleInputBufferImpl inputBuffer = new SimpleInputBufferImpl(inputStream, data);
if(protocol.ver >= 4 && channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
inputBuffer.setToByteArrayReturns(null);
inputBuffer.readByte();
int count = inputBuffer.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputBuffer.readVarInt();
k = inputStream.getReaderIndex() + j;
if(j > inputBuffer.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputBuffer.available());
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputBuffer);
if(pkt != null) {
pkt.handlePacket(handler);
}else {
throw new IOException("Unknown packet type in fragment!");
}
if(inputStream.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + inputStream.getReaderIndex() - k) + " != " + j);
}
}
if(inputBuffer.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputBuffer.available() + " bytes)");
}
return true;
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputBuffer);
if(pkt != null && inputStream.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
}
if(pkt != null) {
pkt.handlePacket(handler);
return true;
}else {
return false;
}
}
public void sendPacketImmediately(GameMessagePacket packet) throws IOException {
sendPacket(packet, true);
}
public void sendPacket(GameMessagePacket packet) throws IOException {
sendPacket(packet, false);
}
protected void sendPacket(GameMessagePacket packet, boolean immediately) throws IOException {
int len = packet.length() + 1;
String chan;
byte[] data;
if(outputStreamLock.tryLock()) {
try {
byteOutputStreamSingleton.feedBuffer(new byte[len == 0 ? 64 : len]);
chan = protocol.writePacket(GamePluginMessageConstants.SERVER_TO_CLIENT, outputStreamSingleton, packet);
data = byteOutputStreamSingleton.returnBuffer();
}finally {
byteOutputStreamSingleton.feedBuffer(null);
outputStreamLock.unlock();
}
}else {
// slow version that makes multiple new objects
ReusableByteArrayOutputStream bao = new ReusableByteArrayOutputStream();
bao.feedBuffer(new byte[len == 0 ? 64 : len]);
SimpleOutputBufferImpl outputStream = new SimpleOutputBufferImpl(bao);
chan = protocol.writePacket(GamePluginMessageConstants.SERVER_TO_CLIENT, outputStream, packet);
data = bao.returnBuffer();
}
if(len != 0 && data.length != len && data.length + 1 != len) {
EaglerXBungee.logger().warning("Packet " + packet.getClass().getSimpleName()
+ " was the wrong length after serialization, " + data.length + " != " + len);
}
if(defagSendDelay > 0 && protocol.ver >= 4 && chan.equals(GamePluginMessageConstants.V4_CHANNEL)) {
synchronized(sendQueueV4) {
int varIntLen = GamePacketOutputBuffer.getVarIntSize(data.length);
if(immediately || sendQueueByteLengthV4 + data.length + varIntLen > 32760) {
if(futureSendTaskV4 != null && !futureSendTaskV4.isDone()) {
futureSendTaskV4.cancel(false);
futureSendTaskV4 = null;
futureSendCallableV4 = null;
}
if(!sendQueueV4.isEmpty()) {
flushQueue();
}
}
if(immediately) {
owner.sendData(chan, data);
}else {
sendQueueV4.add(data);
if(futureSendTaskV4 == null || futureSendTaskV4.isDone()) {
futureSendTaskV4 = ((EaglerInitialHandler) owner.getPendingConnection()).ch.getHandle()
.eventLoop().schedule(futureSendCallableV4 = new Callable<Void>() {
@Override
public Void call() throws Exception {
synchronized (sendQueueV4) {
if (futureSendCallableV4 != this) {
return null;
}
futureSendTaskV4 = null;
futureSendCallableV4 = null;
if(!sendQueueV4.isEmpty()) {
flushQueue();
}
}
return null;
}
}, defagSendDelay, TimeUnit.MILLISECONDS);
}
}
}
}else {
owner.sendData(chan, data);
}
}
private void flushQueue() {
if(!owner.isConnected()) {
sendQueueV4.clear();
return;
}
byte[] pkt;
if(sendQueueV4.size() == 1) {
pkt = sendQueueV4.remove(0);
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, pkt);
}else {
int i, j, sendCount = 0, totalLen = 0;
while(!sendQueueV4.isEmpty()) {
do {
i = sendQueueV4.get(sendCount++).length;
totalLen += GamePacketOutputBuffer.getVarIntSize(i) + i;
}while(totalLen < 32760 && sendCount < sendQueueV4.size());
if(totalLen >= 32760) {
--sendCount;
}
if(sendCount <= 1) {
pkt = sendQueueV4.remove(0);
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, pkt);
continue;
}
byte[] toSend = new byte[1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)];
ByteBuf sendBuffer = Unpooled.wrappedBuffer(toSend);
sendBuffer.writerIndex(0);
sendBuffer.writeByte(0xFF);
DefinedPacket.writeVarInt(sendCount, sendBuffer);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
DefinedPacket.writeVarInt(pkt.length, sendBuffer);
sendBuffer.writeBytes(pkt);
}
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, toSend);
}
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, UserConnection conn, EaglerXBungee plugin) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(conn, plugin);
case 4:
return new ServerV4MessageHandler(conn, plugin);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static void sendHelper(UserConnection conn, GameMessagePacket packet) {
PendingConnection p = conn.getPendingConnection();
if(p instanceof EaglerInitialHandler) {
((EaglerInitialHandler)p).sendEaglerMessage(packet);
}else {
throw new UnsupportedOperationException("Tried to send eagler packet on a non-eagler connection!");
}
}
public UserConnection getUserConnection() {
return owner;
}
}

View File

@ -0,0 +1,88 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.md_5.bungee.UserConnection;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ServerV3MessageHandler implements GameMessageHandler {
private final UserConnection conn;
private final EaglerXBungee plugin;
public ServerV3MessageHandler(UserConnection conn, EaglerXBungee plugin) {
this.conn = conn;
this.plugin = plugin;
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
plugin.getCapeService().processGetOtherCape(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), packet.url, conn);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeConnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDesc(new UUID(packet.uuidMost, packet.uuidLeast), packet.desc, conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV3EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
if(packet.isPeerType) {
svc.handleVoiceSignalPacketTypeDisconnectPeer(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}else {
svc.handleVoiceSignalPacketTypeDisconnect(conn);
}
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeICE(new UUID(packet.uuidMost, packet.uuidLeast), packet.ice, conn);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeRequest(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
}

View File

@ -0,0 +1,182 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.util.Arrays;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewMessage;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewOpenClose;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebViewChannelEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebViewMessageEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ServerV4MessageHandler implements GameMessageHandler {
private final UserConnection conn;
private final EaglerXBungee plugin;
public ServerV4MessageHandler(UserConnection conn, EaglerXBungee plugin) {
this.conn = conn;
this.plugin = plugin;
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
plugin.getCapeService().processGetOtherCape(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), packet.url, conn);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeConnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDesc(new UUID(packet.uuidMost, packet.uuidLeast), packet.desc, conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectPeerV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnectPeer(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeICE(new UUID(packet.uuidMost, packet.uuidLeast), packet.ice, conn);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeRequest(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
public void handleClient(CPacketGetOtherClientUUIDV4EAG packet) {
ProxiedPlayer player = plugin.getProxy().getPlayer(new UUID(packet.playerUUIDMost, packet.playerUUIDLeast));
if(player != null) {
PendingConnection conn2 = player.getPendingConnection();
if(conn2 instanceof EaglerInitialHandler) {
UUID uuid = ((EaglerInitialHandler)conn2).getClientBrandUUID();
if (uuid != null) {
((EaglerInitialHandler) conn.getPendingConnection())
.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()));
return;
}
}else {
((EaglerInitialHandler) conn.getPendingConnection())
.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID.getMostSignificantBits(),
EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID.getLeastSignificantBits()));
return;
}
}
((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId, 0l, 0l));
}
public void handleClient(CPacketRequestServerInfoV4EAG packet) {
EaglerPauseMenuConfig conf = plugin.getConfig().getPauseMenuConf();
if (conf != null && conf.getEnabled()
&& conf.getPacket().serverInfoMode == SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS
&& Arrays.equals(conf.getServerInfoHash(), packet.requestHash)) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
synchronized(eaglerHandler.serverInfoSendBuffer) {
if(eaglerHandler.hasSentServerInfo.getAndSet(true)) {
conn.disconnect(new TextComponent("Duplicate server info request"));
return;
}
eaglerHandler.serverInfoSendBuffer.clear();
eaglerHandler.serverInfoSendBuffer.addAll(conf.getServerInfo());
}
}else {
conn.disconnect(new TextComponent("Invalid server info request"));
}
}
public void handleClient(CPacketWebViewMessageV4EAG packet) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
if(eaglerHandler.isWebViewChannelAllowed) {
if(eaglerHandler.webViewMessageChannelOpen.get()) {
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_MESSAGE)) {
eaglerHandler.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewMessage(
eaglerHandler.webViewMessageChannelName, packet.type, packet.data));
}
BungeeCord.getInstance().getPluginManager().callEvent(new EaglercraftWebViewMessageEvent(conn,
eaglerHandler.getEaglerListenerConfig(), eaglerHandler.webViewMessageChannelName, packet));
}
}else {
conn.disconnect(new TextComponent("Webview channel permissions have not been enabled!"));
}
}
public void handleClient(CPacketWebViewMessageEnV4EAG packet) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
if(eaglerHandler.isWebViewChannelAllowed) {
eaglerHandler.webViewMessageChannelOpen.set(packet.messageChannelOpen);
String oldChannelName = eaglerHandler.webViewMessageChannelName;
eaglerHandler.webViewMessageChannelName = packet.messageChannelOpen ? packet.channelName : null;
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_OPEN_CLOSE)) {
eaglerHandler.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewOpenClose(
packet.messageChannelOpen, packet.messageChannelOpen ? packet.channelName : oldChannelName));
}
BungeeCord.getInstance().getPluginManager()
.callEvent(new EaglercraftWebViewChannelEvent(conn, eaglerHandler.getEaglerListenerConfig(),
packet.messageChannelOpen ? eaglerHandler.webViewMessageChannelName : oldChannelName,
packet.messageChannelOpen ? EaglercraftWebViewChannelEvent.EventType.CHANNEL_OPEN
: EaglercraftWebViewChannelEvent.EventType.CHANNEL_CLOSE));
}else {
conn.disconnect(new TextComponent("Webview channel permissions have not been enabled!"));
}
}
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
@ -47,7 +48,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
@Override @Override
protected void begin(String queryType) { protected void begin(String queryType) {
creationTime = System.currentTimeMillis(); creationTime = EaglerXBungeeAPIHelper.steadyTimeMillis();
subType = queryType; subType = queryType;
returnType = "MOTD"; returnType = "MOTD";
EaglerListenerConfig listener = getListener(); EaglerListenerConfig listener = getListener();
@ -60,7 +61,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
} }
maxPlayers = listener.getMaxPlayers(); maxPlayers = listener.getMaxPlayers();
onlinePlayers = ProxyServer.getInstance().getOnlineCount(); onlinePlayers = ProxyServer.getInstance().getOnlineCount();
players = new ArrayList(); players = new ArrayList<>();
for(ProxiedPlayer pp : ProxyServer.getInstance().getPlayers()) { for(ProxiedPlayer pp : ProxyServer.getInstance().getPlayers()) {
players.add(pp.getDisplayName()); players.add(pp.getDisplayName());
if(players.size() >= 9) { if(players.size() >= 9) {

View File

@ -28,12 +28,13 @@ import net.md_5.bungee.api.plugin.PluginDescription;
*/ */
public class QueryManager { public class QueryManager {
private static final Map<String, Class<? extends HttpServerQueryHandler>> queryTypes = new HashMap(); private static final Map<String, Class<? extends HttpServerQueryHandler>> queryTypes = new HashMap<>();
static { static {
queryTypes.put("motd", MOTDQueryHandler.class); queryTypes.put("motd", MOTDQueryHandler.class);
queryTypes.put("motd.cache", MOTDQueryHandler.class); queryTypes.put("motd.cache", MOTDQueryHandler.class);
queryTypes.put("version", VersionQueryHandler.class); queryTypes.put("version", VersionQueryHandler.class);
queryTypes.put("revoke_session_token", RevokeSessionQueryHandler.class);
} }
public static HttpServerQueryHandler createQueryHandler(String type) { public static HttpServerQueryHandler createQueryHandler(String type) {

View File

@ -0,0 +1,74 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRevokeSessionQueryEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRevokeSessionQueryEvent.EnumSessionRevokeStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQueryHandler;
import net.md_5.bungee.BungeeCord;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class RevokeSessionQueryHandler extends EaglerQueryHandler {
@Override
protected void begin(String queryType) {
this.setKeepAlive(true);
this.acceptBinary();
this.setMaxAge(5000l);
this.sendStringResponse("revoke_session_token", "ready");
}
@Override
protected void processString(String str) {
this.close();
}
@Override
protected void processJson(JsonObject obj) {
this.close();
}
@Override
protected void processBytes(byte[] bytes) {
if(bytes.length > 255) {
JsonObject response = new JsonObject();
response.addProperty("status", "error");
response.addProperty("code", 3);
response.addProperty("delete", false);
sendJsonResponseAndClose("revoke_session_token", response);
return;
}
this.setMaxAge(30000l);
EaglercraftRevokeSessionQueryEvent evt = new EaglercraftRevokeSessionQueryEvent(this.getAddress(), this.getOrigin(), bytes, this);
BungeeCord.getInstance().getPluginManager().callEvent(evt);
JsonObject response = new JsonObject();
EnumSessionRevokeStatus stat = evt.getResultStatus();
response.addProperty("status", stat.status);
if(stat.code != -1) {
response.addProperty("code", stat.code);
}
if(stat != EnumSessionRevokeStatus.SUCCESS) {
response.addProperty("delete", evt.getShouldDeleteCookie());
}
sendJsonResponseAndClose("revoke_session_token", response);
}
@Override
protected void closed() {
}
}

View File

@ -27,8 +27,13 @@ public class VersionQueryHandler extends EaglerQuerySimpleHandler {
protected void begin(String queryType) { protected void begin(String queryType) {
JsonObject responseObj = new JsonObject(); JsonObject responseObj = new JsonObject();
JsonArray handshakeVersions = new JsonArray(); JsonArray handshakeVersions = new JsonArray();
if(this.getListener().isAllowV3()) {
handshakeVersions.add(2); handshakeVersions.add(2);
handshakeVersions.add(3); handshakeVersions.add(3);
}
if(this.getListener().isAllowV4()) {
handshakeVersions.add(4);
}
responseObj.add("handshakeVersions", handshakeVersions); responseObj.add("handshakeVersions", handshakeVersions);
JsonArray protocolVersions = new JsonArray(); JsonArray protocolVersions = new JsonArray();
protocolVersions.add(47); protocolVersions.add(47);

View File

@ -27,7 +27,7 @@ public class HttpContentType {
public final String cacheControlHeader; public final String cacheControlHeader;
public final long fileBrowserCacheTTL; public final long fileBrowserCacheTTL;
public static final HttpContentType defaultType = new HttpContentType(new HashSet(), "application/octet-stream", null, 14400000l); public static final HttpContentType defaultType = new HttpContentType(new HashSet<>(), "application/octet-stream", null, 14400000l);
public HttpContentType(Set<String> extensions, String mimeType, String charset, long fileBrowserCacheTTL) { public HttpContentType(Set<String> extensions, String mimeType, String charset, long fileBrowserCacheTTL) {
this.extensions = extensions; this.extensions = extensions;

View File

@ -4,16 +4,17 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -44,13 +45,13 @@ public class HttpWebServer {
public HttpWebServer(File directory, Map<String,HttpContentType> contentTypes, List<String> index, String page404) { public HttpWebServer(File directory, Map<String,HttpContentType> contentTypes, List<String> index, String page404) {
this.directory = directory; this.directory = directory;
this.contentTypes = contentTypes; this.contentTypes = contentTypes;
this.filesCache = new HashMap(); this.filesCache = new HashMap<>();
this.index = index; this.index = index;
this.page404 = page404; this.page404 = page404;
} }
public void flushCache() { public void flushCache() {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
synchronized(cacheClearLock) { synchronized(cacheClearLock) {
synchronized(filesCache) { synchronized(filesCache) {
Iterator<HttpMemoryCache> itr = filesCache.values().iterator(); Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
@ -69,7 +70,7 @@ public class HttpWebServer {
try { try {
String[] pathSplit = path.split("(\\\\|\\/)+"); String[] pathSplit = path.split("(\\\\|\\/)+");
List<String> pathList = pathSplit.length == 0 ? null : new ArrayList(); List<String> pathList = pathSplit.length == 0 ? null : new ArrayList<>(pathSplit.length);
for(int i = 0; i < pathSplit.length; ++i) { for(int i = 0; i < pathSplit.length; ++i) {
pathSplit[i] = pathSplit[i].trim(); pathSplit[i] = pathSplit[i].trim();
if(pathSplit[i].length() > 0) { if(pathSplit[i].length() > 0) {
@ -197,7 +198,7 @@ public class HttpWebServer {
if(ct == null) { if(ct == null) {
ct = HttpContentType.defaultType; ct = HttpContentType.defaultType;
} }
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(path, requestCachePath, file, ct, millis, millis, path.lastModified()); return new HttpMemoryCache(path, requestCachePath, file, ct, millis, millis, path.lastModified());
}catch(Throwable t) { }catch(Throwable t) {
return null; return null;
@ -208,7 +209,7 @@ public class HttpWebServer {
if(file.fileObject == null) { if(file.fileObject == null) {
return file; return file;
} }
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
file.lastCacheHit = millis; file.lastCacheHit = millis;
if(millis - file.lastDiskReload > 4000l) { if(millis - file.lastDiskReload > 4000l) {
File f = file.fileObject; File f = file.fileObject;
@ -264,8 +265,8 @@ public class HttpWebServer {
+ "The requested resource <span id=\"addr\" style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">" + "The requested resource <span id=\"addr\" style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">"
+ "</span> could not be found on this server!</p><p>" + htmlEntities(plugin.getDescription().getName()) + "/" + "</span> could not be found on this server!</p><p>" + htmlEntities(plugin.getDescription().getName()) + "/"
+ htmlEntities(plugin.getDescription().getVersion()) + "</p></body></html>").getBytes(StandardCharsets.UTF_8); + htmlEntities(plugin.getDescription().getVersion()) + "</p></body></html>").getBytes(StandardCharsets.UTF_8);
HttpContentType htmlContentType = new HttpContentType(new HashSet(Arrays.asList("html")), "text/html", "utf-8", 120000l); HttpContentType htmlContentType = new HttpContentType(Sets.newHashSet("html"), "text/html", "utf-8", 120000l);
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis); return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
} }
@ -280,8 +281,8 @@ public class HttpWebServer {
+ "404 'Websocket Upgrade Failure' (rip)</h1><h3>The URL you have requested is the physical WebSocket address of '" + name + "'</h3><p style=\"font-size:1.2em;" + "404 'Websocket Upgrade Failure' (rip)</h1><h3>The URL you have requested is the physical WebSocket address of '" + name + "'</h3><p style=\"font-size:1.2em;"
+ "line-height:1.3em;\">To correctly join this server, load the latest EaglercraftX 1.8 client, click the 'Direct Connect' button<br />on the 'Multiplayer' screen, " + "line-height:1.3em;\">To correctly join this server, load the latest EaglercraftX 1.8 client, click the 'Direct Connect' button<br />on the 'Multiplayer' screen, "
+ "and enter <span id=\"wsUri\">this URL</span> as the server address</p></body></html>").getBytes(StandardCharsets.UTF_8); + "and enter <span id=\"wsUri\">this URL</span> as the server address</p></body></html>").getBytes(StandardCharsets.UTF_8);
HttpContentType htmlContentType = new HttpContentType(new HashSet(Arrays.asList("html")), "text/html", "utf-8", 14400000l); HttpContentType htmlContentType = new HttpContentType(Sets.newHashSet("html"), "text/html", "utf-8", 14400000l);
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis); return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
} }

View File

@ -108,7 +108,7 @@ public class AsyncSkinProvider {
byte[] loadedPixels = new byte[16384]; byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 64, tmp, 0, 64); image.getRGB(0, 0, 64, 64, tmp, 0, 64);
SkinRescaler.convertToBytes(tmp, loadedPixels); SkinRescaler.convertToBytes(tmp, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0); SkinPackets.setAlphaForChestV3(loadedPixels);
doAccept(loadedPixels); doAccept(loadedPixels);
return; return;
}else if(srcWidth == 64 && srcHeight == 32) { }else if(srcWidth == 64 && srcHeight == 32) {
@ -116,7 +116,7 @@ public class AsyncSkinProvider {
byte[] loadedPixels = new byte[16384]; byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 32, tmp1, 0, 64); image.getRGB(0, 0, 64, 32, tmp1, 0, 64);
SkinRescaler.convert64x32To64x64(tmp1, loadedPixels); SkinRescaler.convert64x32To64x64(tmp1, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0); SkinPackets.setAlphaForChestV3(loadedPixels);
doAccept(loadedPixels); doAccept(loadedPixels);
return; return;
}else { }else {

View File

@ -9,6 +9,8 @@ import java.util.function.Consumer;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import org.apache.commons.lang3.ArrayUtils;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
@ -180,7 +182,7 @@ public class BinaryHttpClient {
buffer.readBytes(array); buffer.readBytes(array);
buffer.release(); buffer.release();
}else { }else {
array = new byte[0]; array = ArrayUtils.EMPTY_BYTE_ARRAY;
} }
responseCallback.accept(new Response(responseCode, array)); responseCallback.accept(new Response(responseCode, array));
}finally { }finally {

View File

@ -3,7 +3,9 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.UserConnection; import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
/** /**
* Copyright (c) 2024 lax1dude. All Rights Reserved. * Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -24,56 +26,29 @@ public class CapePackets {
public static final int PACKET_MY_CAPE_PRESET = 0x01; public static final int PACKET_MY_CAPE_PRESET = 0x01;
public static final int PACKET_MY_CAPE_CUSTOM = 0x02; public static final int PACKET_MY_CAPE_CUSTOM = 0x02;
public static final int PACKET_GET_OTHER_CAPE = 0x03;
public static final int PACKET_OTHER_CAPE_PRESET = 0x04;
public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05;
public static void processPacket(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException {
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int packetId = (int)data[0] & 0xFF;
try {
switch(packetId) {
case PACKET_GET_OTHER_CAPE:
processGetOtherCape(data, sender, capeService);
break;
default:
throw new IOException("Unknown packet type " + packetId);
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling cape packet type " + packetId, t);
}
}
private static void processGetOtherCape(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = SkinPackets.bytesToUUID(data, 1);
capeService.processGetOtherCape(searchUUID, sender);
}
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, CapeServiceOffline capeService) throws IOException { public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, CapeServiceOffline capeService) throws IOException {
if(bs.length == 0) { if(bs.length == 0) {
throw new IOException("Zero-length packet recieved"); throw new IOException("Zero-length packet recieved");
} }
byte[] generatedPacket; GameMessagePacket generatedPacket;
int packetType = (int)bs[0] & 0xFF; int packetType = (int)bs[0] & 0xFF;
switch(packetType) { switch(packetType) {
case PACKET_MY_CAPE_PRESET: case PACKET_MY_CAPE_PRESET:
if(bs.length != 5) { if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset cape packet"); throw new IOException("Invalid length " + bs.length + " for preset cape packet");
} }
generatedPacket = CapePackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF)); generatedPacket = new SPacketOtherCapePresetEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break; break;
case PACKET_MY_CAPE_CUSTOM: case PACKET_MY_CAPE_CUSTOM:
if(bs.length != 1174) { if(bs.length != 1174) {
throw new IOException("Invalid length " + bs.length + " for custom cape packet"); throw new IOException("Invalid length " + bs.length + " for custom cape packet");
} }
generatedPacket = CapePackets.makeCustomResponse(clientUUID, bs, 1, 1173); byte[] capePixels = new byte[bs.length - 1];
System.arraycopy(bs, 1, capePixels, 0, capePixels.length);
generatedPacket = new SPacketOtherCapeCustomEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), capePixels);
break; break;
default: default:
throw new IOException("Unknown skin packet type: " + packetType); throw new IOException("Unknown skin packet type: " + packetType);
@ -82,29 +57,8 @@ public class CapePackets {
} }
public static void registerEaglerPlayerFallback(UUID clientUUID, CapeServiceOffline capeService) { public static void registerEaglerPlayerFallback(UUID clientUUID, CapeServiceOffline capeService) {
capeService.registerEaglercraftPlayer(clientUUID, CapePackets.makePresetResponse(clientUUID, 0)); capeService.registerEaglercraftPlayer(clientUUID, new SPacketOtherCapePresetEAG(
clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), 0));
} }
public static byte[] makePresetResponse(UUID uuid, int presetId) {
byte[] ret = new byte[1 + 16 + 4];
ret[0] = (byte)PACKET_OTHER_CAPE_PRESET;
SkinPackets.UUIDToBytes(uuid, ret, 1);
ret[17] = (byte)(presetId >>> 24);
ret[18] = (byte)(presetId >>> 16);
ret[19] = (byte)(presetId >>> 8);
ret[20] = (byte)(presetId & 0xFF);
return ret;
}
public static byte[] makeCustomResponse(UUID uuid, byte[] pixels) {
return makeCustomResponse(uuid, pixels, 0, pixels.length);
}
public static byte[] makeCustomResponse(UUID uuid, byte[] pixels, int offset, int length) {
byte[] ret = new byte[1 + 16 + length];
ret[0] = (byte)PACKET_OTHER_CAPE_CUSTOM;
SkinPackets.UUIDToBytes(uuid, ret, 1);
System.arraycopy(pixels, offset, ret, 17, length);
return ret;
}
} }

View File

@ -5,6 +5,11 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketForceClientCapeCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketForceClientCapePresetV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/** /**
@ -26,26 +31,42 @@ public class CapeServiceOffline {
public static final int masterRateLimitPerPlayer = 250; public static final int masterRateLimitPerPlayer = 250;
public static final String CHANNEL = "EAG|Capes-1.8"; private final Map<UUID, GameMessagePacket> capesCache = new HashMap<>();
private final Map<UUID, byte[]> capesCache = new HashMap(); public void registerEaglercraftPlayer(UUID playerUUID, GameMessagePacket capePacket) {
public void registerEaglercraftPlayer(UUID playerUUID, byte[] capePacket) {
synchronized(capesCache) { synchronized(capesCache) {
capesCache.put(playerUUID, capePacket); capesCache.put(playerUUID, capePacket);
} }
} }
public void processGetOtherCape(UUID searchUUID, UserConnection sender) { public void processGetOtherCape(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) { EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
byte[] maybeCape; if(initialHandler.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
GameMessagePacket maybeCape;
synchronized(capesCache) { synchronized(capesCache) {
maybeCape = capesCache.get(searchUUID); maybeCape = capesCache.get(searchUUID);
} }
if(maybeCape != null) { if(maybeCape != null) {
sender.sendData(CapeServiceOffline.CHANNEL, maybeCape); initialHandler.sendEaglerMessage(maybeCape);
}else { }else {
sender.sendData(CapeServiceOffline.CHANNEL, CapePackets.makePresetResponse(searchUUID, 0)); initialHandler.sendEaglerMessage(new SPacketOtherCapePresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
}
}
}
public void processForceCape(UUID clientUUID, EaglerInitialHandler initialHandler) {
GameMessagePacket maybeCape;
synchronized(capesCache) {
maybeCape = capesCache.get(clientUUID);
}
if(maybeCape != null) {
if (maybeCape instanceof SPacketOtherCapePresetEAG) {
initialHandler.sendEaglerMessage(
new SPacketForceClientCapePresetV4EAG(((SPacketOtherCapePresetEAG) maybeCape).presetCape));
} else if (maybeCape instanceof SPacketOtherCapeCustomEAG) {
initialHandler.sendEaglerMessage(
new SPacketForceClientCapeCustomV4EAG(((SPacketOtherCapeCustomEAG) maybeCape).customCape));
} }
} }
} }
@ -56,6 +77,37 @@ public class CapeServiceOffline {
} }
} }
public GameMessagePacket getCape(UUID clientUUID) {
synchronized(capesCache) {
return capesCache.get(clientUUID);
}
}
public byte[] getCapeHandshakeData(UUID clientUUID) {
GameMessagePacket capePacket = getCape(clientUUID);
if(capePacket != null) {
if(capePacket instanceof SPacketOtherCapeCustomEAG) {
SPacketOtherCapeCustomEAG pkt = (SPacketOtherCapeCustomEAG)capePacket;
byte[] ret = new byte[1174];
ret[0] = (byte)2;
System.arraycopy(pkt.customCape, 0, ret, 1, 1173);
return ret;
}else {
SPacketOtherCapePresetEAG pkt = (SPacketOtherCapePresetEAG)capePacket;
int p = pkt.presetCape;
byte[] ret = new byte[5];
ret[0] = (byte)1;
ret[1] = (byte)(p >>> 24);
ret[2] = (byte)(p >>> 16);
ret[3] = (byte)(p >>> 8);
ret[4] = (byte)(p & 0xFF);
return ret;
}
}else {
return null;
}
}
public void shutdown() { public void shutdown() {
synchronized(capesCache) { synchronized(capesCache) {
capesCache.clear(); capesCache.clear();

View File

@ -1,8 +1,9 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/** /**
@ -29,7 +30,7 @@ public interface ISkinService {
void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender); void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender);
void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException; void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId);
void unregisterPlayer(UUID clientUUID); void unregisterPlayer(UUID clientUUID);
@ -43,4 +44,8 @@ public interface ISkinService {
void shutdown(); void shutdown();
void processForceSkin(UUID playerUUID, EaglerInitialHandler initialHandler);
SkinPacketVersionCache getSkin(UUID playerUUID);
} }

View File

@ -14,7 +14,11 @@ import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite.EaglerDrivers; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite.EaglerDrivers;
/** /**
@ -160,7 +164,7 @@ public class JDBCCacheProvider implements ICacheProvider {
throw new CacheException("SQL query failure while loading cached skin", ex); throw new CacheException("SQL query failure while loading cached skin", ex);
} }
if(queriedLength == 0) { if(queriedLength == 0) {
return new CacheLoadedSkin(uuid, queriedUrls, new byte[0]); return new CacheLoadedSkin(uuid, queriedUrls, ArrayUtils.EMPTY_BYTE_ARRAY);
}else { }else {
byte[] decompressed = new byte[queriedLength]; byte[] decompressed = new byte[queriedLength];
try { try {
@ -305,8 +309,9 @@ public class JDBCCacheProvider implements ICacheProvider {
@Override @Override
public void flush() { public void flush() {
long millis = System.currentTimeMillis(); long millis = System.currentTimeMillis();
if(millis - lastFlush > 1200000l) { // 30 minutes long steadyMillis = EaglerXBungeeAPIHelper.steadyTimeMillis();
lastFlush = millis; if(steadyMillis - lastFlush > 1200000l) { // 30 minutes
lastFlush = steadyMillis;
try { try {
Date expiryObjects = new Date(millis - keepObjectsDays * 86400000l); Date expiryObjects = new Date(millis - keepObjectsDays * 86400000l);
Date expiryProfiles = new Date(millis - keepProfilesDays * 86400000l); Date expiryProfiles = new Date(millis - keepProfilesDays * 86400000l);

View File

@ -1,5 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
* *
@ -21,13 +23,13 @@ public class SimpleRateLimiter {
private int count; private int count;
public SimpleRateLimiter() { public SimpleRateLimiter() {
timer = System.currentTimeMillis(); timer = EaglerXBungeeAPIHelper.steadyTimeMillis();
count = 0; count = 0;
} }
public boolean rateLimit(int maxPerMinute) { public boolean rateLimit(int maxPerMinute) {
int t = 60000 / maxPerMinute; int t = 60000 / maxPerMinute;
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
int decr = (int)(millis - timer) / t; int decr = (int)(millis - timer) / t;
if(decr > 0) { if(decr > 0) {
timer += decr * t; timer += decr * t;

View File

@ -1,12 +1,13 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID; import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.md_5.bungee.UserConnection; import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
/** /**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
@ -27,79 +28,13 @@ public class SkinPackets {
public static final int PACKET_MY_SKIN_PRESET = 0x01; public static final int PACKET_MY_SKIN_PRESET = 0x01;
public static final int PACKET_MY_SKIN_CUSTOM = 0x02; public static final int PACKET_MY_SKIN_CUSTOM = 0x02;
public static final int PACKET_GET_OTHER_SKIN = 0x03;
public static final int PACKET_OTHER_SKIN_PRESET = 0x04;
public static final int PACKET_OTHER_SKIN_CUSTOM = 0x05;
public static final int PACKET_GET_SKIN_BY_URL = 0x06;
public static void processPacket(byte[] data, UserConnection sender, ISkinService skinService) throws IOException { public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, ISkinService skinService, int protocolVers) throws IOException {
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int packetId = (int)data[0] & 0xFF;
try {
switch(packetId) {
case PACKET_GET_OTHER_SKIN:
processGetOtherSkin(data, sender, skinService);
break;
case PACKET_GET_SKIN_BY_URL:
processGetOtherSkinByURL(data, sender, skinService);
break;
default:
throw new IOException("Unknown packet type " + packetId);
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling packet type " + packetId, t);
}
}
private static void processGetOtherSkin(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
skinService.processGetOtherSkin(searchUUID, sender);
}
private static void processGetOtherSkinByURL(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length < 20) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
int urlLength = (data[17] << 8) | data[18];
if(data.length < 19 + urlLength) {
throw new IOException("Invalid length " + data.length + " for skin request packet with " + urlLength + " length URL");
}
String urlStr = bytesToAscii(data, 19, urlLength);
urlStr = SkinService.sanitizeTextureURL(urlStr);
if(urlStr == null) {
throw new IOException("Invalid URL for skin request packet");
}
URL url;
try {
url = new URL(urlStr);
}catch(MalformedURLException t) {
throw new IOException("Invalid URL for skin request packet", t);
}
String host = url.getHost();
if(EaglerXBungee.getEagler().getConfig().isValidSkinHost(host)) {
UUID validUUID = createEaglerURLSkinUUID(urlStr);
if(!searchUUID.equals(validUUID)) {
throw new IOException("Invalid generated UUID from skin URL");
}
skinService.processGetOtherSkin(searchUUID, urlStr, sender);
}else {
throw new IOException("Invalid host in skin packet: " + host);
}
}
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, ISkinService skinService) throws IOException {
if(bs.length == 0) { if(bs.length == 0) {
throw new IOException("Zero-length packet recieved"); throw new IOException("Zero-length packet recieved");
} }
byte[] generatedPacket; GameMessagePacket generatedPacketV3 = null;
GameMessagePacket generatedPacketV4 = null;
int skinModel = -1; int skinModel = -1;
int packetType = (int)bs[0] & 0xFF; int packetType = (int)bs[0] & 0xFF;
switch(packetType) { switch(packetType) {
@ -107,87 +42,60 @@ public class SkinPackets {
if(bs.length != 5) { if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset skin packet"); throw new IOException("Invalid length " + bs.length + " for preset skin packet");
} }
generatedPacket = SkinPackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF)); generatedPacketV3 = generatedPacketV4 = new SPacketOtherSkinPresetEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break; break;
case PACKET_MY_SKIN_CUSTOM: case PACKET_MY_SKIN_CUSTOM:
if(bs.length != 2 + 16384) { if(protocolVers <= 3) {
byte[] pixels = new byte[16384];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet"); throw new IOException("Invalid length " + bs.length + " for custom skin packet");
} }
setAlphaForChest(bs, (byte)255, 2); setAlphaForChestV3(pixels);
generatedPacket = SkinPackets.makeCustomResponse(clientUUID, (skinModel = (int)bs[1] & 0xFF), bs, 2, 16384); System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV3 = new SPacketOtherSkinCustomV3EAG(clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), (skinModel = (int)bs[1] & 0xFF), pixels);
}else {
byte[] pixels = new byte[12288];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChestV4(pixels);
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV4 = new SPacketOtherSkinCustomV4EAG(clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), (skinModel = (int)bs[1] & 0xFF), pixels);
}
break; break;
default: default:
throw new IOException("Unknown skin packet type: " + packetType); throw new IOException("Unknown skin packet type: " + packetType);
} }
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel); skinService.registerEaglercraftPlayer(clientUUID, new SkinPacketVersionCache(generatedPacketV3, generatedPacketV4), skinModel);
} }
public static void registerEaglerPlayerFallback(UUID clientUUID, ISkinService skinService) throws IOException { public static void registerEaglerPlayerFallback(UUID clientUUID, ISkinService skinService) throws IOException {
int skinModel = (clientUUID.hashCode() & 1) != 0 ? 1 : 0; int skinModel = (clientUUID.hashCode() & 1) != 0 ? 1 : 0;
byte[] generatedPacket = SkinPackets.makePresetResponse(clientUUID, skinModel); skinService.registerEaglercraftPlayer(clientUUID, SkinPacketVersionCache.createPreset(
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel); clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), skinModel), skinModel);
} }
public static void setAlphaForChest(byte[] skin64x64, byte alpha, int offset) { public static void setAlphaForChestV3(byte[] skin64x64) {
if(skin64x64.length - offset != 16384) { if(skin64x64.length != 16384) {
throw new IllegalArgumentException("Skin is not 64x64!"); throw new IllegalArgumentException("Skin is not 64x64!");
} }
for(int y = 20; y < 32; ++y) { for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) { for(int x = 16; x < 40; ++x) {
skin64x64[offset + ((y << 8) | (x << 2))] = alpha; skin64x64[(y << 8) | (x << 2)] = (byte)0xFF;
} }
} }
} }
public static byte[] makePresetResponse(UUID uuid) { public static void setAlphaForChestV4(byte[] skin64x64) {
return makePresetResponse(uuid, (uuid.hashCode() & 1) != 0 ? 1 : 0); if(skin64x64.length != 12288) {
throw new IllegalArgumentException("Skin is not 64x64!");
} }
for(int y = 20; y < 32; ++y) {
public static byte[] makePresetResponse(UUID uuid, int presetId) { for(int x = 16; x < 40; ++x) {
byte[] ret = new byte[1 + 16 + 4]; skin64x64[((y << 6) | x) * 3] |= 0x80;
ret[0] = (byte)PACKET_OTHER_SKIN_PRESET;
UUIDToBytes(uuid, ret, 1);
ret[17] = (byte)(presetId >>> 24);
ret[18] = (byte)(presetId >>> 16);
ret[19] = (byte)(presetId >>> 8);
ret[20] = (byte)(presetId & 0xFF);
return ret;
} }
public static byte[] makeCustomResponse(UUID uuid, int model, byte[] pixels) {
return makeCustomResponse(uuid, model, pixels, 0, pixels.length);
} }
public static byte[] makeCustomResponse(UUID uuid, int model, byte[] pixels, int offset, int length) {
byte[] ret = new byte[1 + 16 + 1 + length];
ret[0] = (byte)PACKET_OTHER_SKIN_CUSTOM;
UUIDToBytes(uuid, ret, 1);
ret[17] = (byte)model;
System.arraycopy(pixels, offset, ret, 18, length);
return ret;
}
public static UUID bytesToUUID(byte[] bytes, int off) {
long msb = (((long) bytes[off] & 0xFFl) << 56l) | (((long) bytes[off + 1] & 0xFFl) << 48l)
| (((long) bytes[off + 2] & 0xFFl) << 40l) | (((long) bytes[off + 3] & 0xFFl) << 32l)
| (((long) bytes[off + 4] & 0xFFl) << 24l) | (((long) bytes[off + 5] & 0xFFl) << 16l)
| (((long) bytes[off + 6] & 0xFFl) << 8l) | ((long) bytes[off + 7] & 0xFFl);
long lsb = (((long) bytes[off + 8] & 0xFFl) << 56l) | (((long) bytes[off + 9] & 0xFFl) << 48l)
| (((long) bytes[off + 10] & 0xFFl) << 40l) | (((long) bytes[off + 11] & 0xFFl) << 32l)
| (((long) bytes[off + 12] & 0xFFl) << 24l) | (((long) bytes[off + 13] & 0xFFl) << 16l)
| (((long) bytes[off + 14] & 0xFFl) << 8l) | ((long) bytes[off + 15] & 0xFFl);
return new UUID(msb, lsb);
}
private static final String hex = "0123456789abcdef";
public static String bytesToString(byte[] bytes, int off, int len) {
char[] ret = new char[len << 1];
for(int i = 0; i < len; ++i) {
ret[i * 2] = hex.charAt((bytes[off + i] >> 4) & 0xF);
ret[i * 2 + 1] = hex.charAt(bytes[off + i] & 0xF);
}
return new String(ret);
} }
public static String bytesToAscii(byte[] bytes, int off, int len) { public static String bytesToAscii(byte[] bytes, int off, int len) {
@ -202,27 +110,6 @@ public class SkinPackets {
return bytesToAscii(bytes, 0, bytes.length); return bytesToAscii(bytes, 0, bytes.length);
} }
public static void UUIDToBytes(UUID uuid, byte[] bytes, int off) {
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
bytes[off] = (byte)(msb >>> 56l);
bytes[off + 1] = (byte)(msb >>> 48l);
bytes[off + 2] = (byte)(msb >>> 40l);
bytes[off + 3] = (byte)(msb >>> 32l);
bytes[off + 4] = (byte)(msb >>> 24l);
bytes[off + 5] = (byte)(msb >>> 16l);
bytes[off + 6] = (byte)(msb >>> 8l);
bytes[off + 7] = (byte)(msb & 0xFFl);
bytes[off + 8] = (byte)(lsb >>> 56l);
bytes[off + 9] = (byte)(lsb >>> 48l);
bytes[off + 10] = (byte)(lsb >>> 40l);
bytes[off + 11] = (byte)(lsb >>> 32l);
bytes[off + 12] = (byte)(lsb >>> 24l);
bytes[off + 13] = (byte)(lsb >>> 16l);
bytes[off + 14] = (byte)(lsb >>> 8l);
bytes[off + 15] = (byte)(lsb & 0xFFl);
}
public static byte[] asciiString(String string) { public static byte[] asciiString(String string) {
byte[] str = new byte[string.length()]; byte[] str = new byte[string.length()];
for(int i = 0; i < str.length; ++i) { for(int i = 0; i < str.length; ++i) {
@ -239,21 +126,4 @@ public class SkinPackets {
return "slim".equalsIgnoreCase(modelName) ? 1 : 0; return "slim".equalsIgnoreCase(modelName) ? 1 : 0;
} }
public static byte[] rewriteUUID(UUID newUUID, byte[] pkt) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
return ret;
}
public static byte[] rewriteUUIDModel(UUID newUUID, byte[] pkt, int model) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
if(ret[0] == (byte)PACKET_OTHER_SKIN_CUSTOM) {
ret[17] = (byte)model;
}
return ret;
}
} }

View File

@ -1,6 +1,5 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -23,18 +22,23 @@ import com.google.gson.JsonParser;
import gnu.trove.map.TObjectIntMap; import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.map.hash.TObjectIntHashMap;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.AsyncSkinProvider.CacheFetchedProfile; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.AsyncSkinProvider.CacheFetchedProfile;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.AsyncSkinProvider.CancelException; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.AsyncSkinProvider.CancelException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketForceClientSkinPresetV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.Property; import net.md_5.bungee.protocol.Property;
/** /**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved. * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -52,19 +56,17 @@ public class SkinService implements ISkinService {
public static final int masterRateLimitPerPlayer = 250; public static final int masterRateLimitPerPlayer = 250;
public static final String CHANNEL = "EAG|Skins-1.8"; private final Map<UUID, CachedPlayerSkin> onlinePlayersCache = new HashMap<>();
private final Map<UUID, CachedPlayerSkin> onlinePlayersCache = new HashMap();
private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
private final Map<UUID, UUID> onlinePlayersToTexturesMap = new HashMap(); private final Map<UUID, UUID> onlinePlayersToTexturesMap = new HashMap<>();
private final Map<UUID, CachedForeignSkin> foreignSkinCache = new HashMap(); private final Map<UUID, CachedForeignSkin> foreignSkinCache = new HashMap<>();
private final Map<UUID, PendingTextureDownload> pendingTextures = new HashMap(); private final Map<UUID, PendingTextureDownload> pendingTextures = new HashMap<>();
private final Map<UUID, PendingProfileUUIDLookup> pendingUUIDs = new HashMap(); private final Map<UUID, PendingProfileUUIDLookup> pendingUUIDs = new HashMap<>();
private final Map<String, PendingProfileNameLookup> pendingNameLookups = new HashMap(); private final Map<String, PendingProfileNameLookup> pendingNameLookups = new HashMap<>();
private final TObjectIntMap<UUID> antagonists = new TObjectIntHashMap(); private final TObjectIntMap<UUID> antagonists = new TObjectIntHashMap<>();
private long antagonistCooldown = System.currentTimeMillis(); private long antagonistCooldown = EaglerXBungeeAPIHelper.steadyTimeMillis();
private final Consumer<Set<UUID>> antagonistLogger = new Consumer<Set<UUID>>() { private final Consumer<Set<UUID>> antagonistLogger = new Consumer<Set<UUID>>() {
@ -93,26 +95,26 @@ public class SkinService implements ISkinService {
protected static class CachedForeignSkin { protected static class CachedForeignSkin {
protected final UUID uuid; protected final UUID uuid;
protected final byte[] data; protected final SkinPacketVersionCache data;
protected final int modelKnown; protected final int modelKnown;
protected long lastHit; protected long lastHit;
protected CachedForeignSkin(UUID uuid, byte[] data, int modelKnown) { protected CachedForeignSkin(UUID uuid, SkinPacketVersionCache data, int modelKnown) {
this.uuid = uuid; this.uuid = uuid;
this.data = data; this.data = data;
this.modelKnown = modelKnown; this.modelKnown = modelKnown;
this.lastHit = System.currentTimeMillis(); this.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
} }
} }
protected static class CachedPlayerSkin { protected static class CachedPlayerSkin {
protected final byte[] data; protected final SkinPacketVersionCache data;
protected final UUID textureUUID; protected final UUID textureUUID;
protected final int modelId; protected final int modelId;
protected CachedPlayerSkin(byte[] data, UUID textureUUID, int modelId) { protected CachedPlayerSkin(SkinPacketVersionCache data, UUID textureUUID, int modelId) {
this.data = data; this.data = data;
this.textureUUID = textureUUID; this.textureUUID = textureUUID;
this.modelId = modelId; this.modelId = modelId;
@ -136,12 +138,12 @@ public class SkinService implements ISkinService {
Consumer<Set<UUID>> antagonistsCallback) { Consumer<Set<UUID>> antagonistsCallback) {
this.textureUUID = textureUUID; this.textureUUID = textureUUID;
this.textureURL = textureURL; this.textureURL = textureURL;
this.antagonists = new LinkedHashSet(); this.antagonists = new LinkedHashSet<>();
this.antagonists.add(caller); this.antagonists.add(caller);
this.callbacks = new LinkedList(); this.callbacks = new LinkedList<>();
this.callbacks.add(callback); this.callbacks.add(callback);
this.antagonistsCallback = antagonistsCallback; this.antagonistsCallback = antagonistsCallback;
this.initializedTime = System.currentTimeMillis(); this.initializedTime = EaglerXBungeeAPIHelper.steadyTimeMillis();
this.finalized = false; this.finalized = false;
} }
@ -177,12 +179,12 @@ public class SkinService implements ISkinService {
protected PendingProfileUUIDLookup(UUID profileUUID, UUID caller, Consumer<CacheFetchedProfile> callback, protected PendingProfileUUIDLookup(UUID profileUUID, UUID caller, Consumer<CacheFetchedProfile> callback,
Consumer<Set<UUID>> antagonistsCallback) { Consumer<Set<UUID>> antagonistsCallback) {
this.profileUUID = profileUUID; this.profileUUID = profileUUID;
this.antagonists = new LinkedHashSet(); this.antagonists = new LinkedHashSet<>();
this.antagonists.add(caller); this.antagonists.add(caller);
this.callbacks = new LinkedList(); this.callbacks = new LinkedList<>();
this.callbacks.add(callback); this.callbacks.add(callback);
this.antagonistsCallback = antagonistsCallback; this.antagonistsCallback = antagonistsCallback;
this.initializedTime = System.currentTimeMillis(); this.initializedTime = EaglerXBungeeAPIHelper.steadyTimeMillis();
this.finalized = false; this.finalized = false;
} }
@ -218,12 +220,12 @@ public class SkinService implements ISkinService {
protected PendingProfileNameLookup(String profileName, UUID caller, Consumer<CacheFetchedProfile> callback, protected PendingProfileNameLookup(String profileName, UUID caller, Consumer<CacheFetchedProfile> callback,
Consumer<Set<UUID>> antagonistsCallback) { Consumer<Set<UUID>> antagonistsCallback) {
this.profileName = profileName; this.profileName = profileName;
this.antagonists = new LinkedHashSet(); this.antagonists = new LinkedHashSet<>();
this.antagonists.add(caller); this.antagonists.add(caller);
this.callbacks = new LinkedList(); this.callbacks = new LinkedList<>();
this.callbacks.add(callback); this.callbacks.add(callback);
this.antagonistsCallback = antagonistsCallback; this.antagonistsCallback = antagonistsCallback;
this.initializedTime = System.currentTimeMillis(); this.initializedTime = EaglerXBungeeAPIHelper.steadyTimeMillis();
this.finalized = false; this.finalized = false;
} }
@ -247,7 +249,7 @@ public class SkinService implements ISkinService {
public void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays, public void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays,
int maxObjects, int maxProfiles) { int maxObjects, int maxProfiles) {
antagonistCooldown = System.currentTimeMillis(); antagonistCooldown = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(cacheProvider == null) { if(cacheProvider == null) {
cacheProvider = JDBCCacheProvider.initialize(uri, driverClass, driverPath, keepObjectsDays, cacheProvider = JDBCCacheProvider.initialize(uri, driverClass, driverPath, keepObjectsDays,
keepProfilesDays, maxObjects, maxProfiles); keepProfilesDays, maxObjects, maxProfiles);
@ -267,7 +269,7 @@ public class SkinService implements ISkinService {
} }
if(maybeCachedPacket != null) { if(maybeCachedPacket != null) {
sender.sendData(SkinService.CHANNEL, maybeCachedPacket.data); eaglerHandler.sendEaglerMessage(maybeCachedPacket.data.get(eaglerHandler.getEaglerProtocol()));
}else { }else {
ProxiedPlayer player = BungeeCord.getInstance().getPlayer(searchUUID); ProxiedPlayer player = BungeeCord.getInstance().getPlayer(searchUUID);
UUID playerTexture; UUID playerTexture;
@ -286,14 +288,16 @@ public class SkinService implements ISkinService {
maybeCachedPacket = onlinePlayersCache.get(uuid); maybeCachedPacket = onlinePlayersCache.get(uuid);
} }
if(maybeCachedPacket != null) { if(maybeCachedPacket != null) {
byte[] rewritten = SkinPackets.rewriteUUID(searchUUID, maybeCachedPacket.data); SkinPacketVersionCache rewritten = SkinPacketVersionCache.rewriteUUID(
maybeCachedPacket.data, searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits());
if(player != null) { if(player != null) {
synchronized(onlinePlayersCache) { synchronized(onlinePlayersCache) {
onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewritten, onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewritten,
maybeCachedPacket.textureUUID, maybeCachedPacket.modelId)); maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
} }
} }
sender.sendData(SkinService.CHANNEL, rewritten); eaglerHandler.sendEaglerMessage(rewritten.get(eaglerHandler.getEaglerProtocol()));
return; return;
} }
} }
@ -306,21 +310,21 @@ public class SkinService implements ISkinService {
if(player != null) { if(player != null) {
synchronized(onlinePlayersCache) { synchronized(onlinePlayersCache) {
onlinePlayersCache.put(searchUUID, onlinePlayersCache.put(searchUUID,
new CachedPlayerSkin(SkinPackets.rewriteUUID(searchUUID, foreignSkin.data), new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()),
playerTexture, foreignSkin.modelKnown)); playerTexture, foreignSkin.modelKnown));
} }
synchronized(foreignSkinCache) { synchronized(foreignSkinCache) {
foreignSkinCache.remove(playerTexture); foreignSkinCache.remove(playerTexture);
} }
}else { }else {
foreignSkin.lastHit = System.currentTimeMillis(); foreignSkin.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
} }
sender.sendData(SkinService.CHANNEL, foreignSkin.data); eaglerHandler.sendEaglerMessage(foreignSkin.data.get(eaglerHandler.getEaglerProtocol()));
return; return;
} }
} }
if(player != null && (player instanceof UserConnection)) { if(player != null && (player instanceof UserConnection)) {
if(player instanceof UserConnection) {
LoginResult loginProfile = ((UserConnection)player).getPendingConnection().getLoginProfile(); LoginResult loginProfile = ((UserConnection)player).getPendingConnection().getLoginProfile();
if(loginProfile != null) { if(loginProfile != null) {
Property[] props = loginProfile.getProperties(); Property[] props = loginProfile.getProperties();
@ -335,8 +339,7 @@ public class SkinService implements ISkinService {
if(skinObj != null) { if(skinObj != null) {
JsonElement url = json.get("url"); JsonElement url = json.get("url");
if(url != null) { if(url != null) {
String urlStr = url.getAsString(); String urlStr = SkinService.sanitizeTextureURL(url.getAsString());
urlStr = SkinService.sanitizeTextureURL(urlStr);
if(urlStr == null) { if(urlStr == null) {
break; break;
} }
@ -356,19 +359,24 @@ public class SkinService implements ISkinService {
} }
if(foreignSkin != null) { if(foreignSkin != null) {
registerTextureToPlayerAssociation(skinUUID, searchUUID); registerTextureToPlayerAssociation(skinUUID, searchUUID);
byte[] rewrite = SkinPackets.rewriteUUIDModel(searchUUID, foreignSkin.data, model); SkinPacketVersionCache rewrite = SkinPacketVersionCache
.rewriteUUIDModel(foreignSkin.data,
searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), model);
synchronized(onlinePlayersCache) { synchronized(onlinePlayersCache) {
onlinePlayersCache.put(searchUUID, new CachedPlayerSkin( onlinePlayersCache.put(searchUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
rewrite, skinUUID, model));
} }
sender.sendData(SkinService.CHANNEL, rewrite); eaglerHandler.sendEaglerMessage(rewrite.get(eaglerHandler.getEaglerProtocol()));
return; return;
} }
// download player skin, put in onlinePlayersCache, no limit // download player skin, put in onlinePlayersCache, no limit
if(!isLimitedAsAntagonist(player.getUniqueId())) { if(!isLimitedAsAntagonist(player.getUniqueId())) {
processResolveURLTextureForOnline(sender, searchUUID, skinUUID, urlStr, model); final int modelf = model;
doAsync(() -> {
processResolveURLTextureForOnline(sender, searchUUID, skinUUID, urlStr, modelf);
});
} }
return; return;
@ -380,13 +388,14 @@ public class SkinService implements ISkinService {
} }
} }
} }
}
if(!isLimitedAsAntagonist(player.getUniqueId())) { if(!isLimitedAsAntagonist(player.getUniqueId())) {
doAsync(() -> {
if(player.getPendingConnection().isOnlineMode()) { if(player.getPendingConnection().isOnlineMode()) {
processResolveProfileTextureByUUIDForOnline(sender, searchUUID); processResolveProfileTextureByUUIDForOnline(sender, searchUUID);
}else { }else {
processResolveProfileTextureByNameForOnline(sender, player.getPendingConnection().getName(), searchUUID); processResolveProfileTextureByNameForOnline(sender, player.getPendingConnection().getName(), searchUUID);
} }
});
} }
}else { }else {
CachedForeignSkin foreignSkin; CachedForeignSkin foreignSkin;
@ -394,13 +403,21 @@ public class SkinService implements ISkinService {
foreignSkin = foreignSkinCache.get(searchUUID); foreignSkin = foreignSkinCache.get(searchUUID);
} }
if(foreignSkin != null) { if(foreignSkin != null) {
foreignSkin.lastHit = System.currentTimeMillis(); foreignSkin.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
sender.sendData(SkinService.CHANNEL, foreignSkin.data); eaglerHandler.sendEaglerMessage(foreignSkin.data.get(eaglerHandler.getEaglerProtocol()));
}else { }else {
if (eaglerHandler.skinUUIDLookupRateLimiter if (eaglerHandler.skinUUIDLookupRateLimiter
.rateLimit(EaglerXBungee.getEagler().getConfig().getUuidRateLimitPlayer()) .rateLimit(EaglerXBungee.getEagler().getConfig().getUuidRateLimitPlayer())
&& !isLimitedAsAntagonist(sender.getUniqueId())) { && !isLimitedAsAntagonist(sender.getUniqueId())) {
if(eaglerHandler.isOnlineMode()) {
doAsync(() -> {
processResolveProfileTextureByUUIDForeign(sender, searchUUID); processResolveProfileTextureByUUIDForeign(sender, searchUUID);
});
}else {
eaglerHandler.sendEaglerMessage(
new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), isAlex(searchUUID) ? 1 : 0));
}
} }
} }
} }
@ -418,8 +435,8 @@ public class SkinService implements ISkinService {
foreignSkin = foreignSkinCache.get(searchUUID); foreignSkin = foreignSkinCache.get(searchUUID);
} }
if(foreignSkin != null) { if(foreignSkin != null) {
foreignSkin.lastHit = System.currentTimeMillis(); foreignSkin.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
sender.sendData(SkinService.CHANNEL, foreignSkin.data); eaglerHandler.sendEaglerMessage(foreignSkin.data.get(eaglerHandler.getEaglerProtocol()));
}else { }else {
Collection<UUID> possiblePlayers; Collection<UUID> possiblePlayers;
synchronized(onlinePlayersFromTexturesMap) { synchronized(onlinePlayersFromTexturesMap) {
@ -433,17 +450,21 @@ public class SkinService implements ISkinService {
maybeCachedPacket = onlinePlayersCache.get(uuid); maybeCachedPacket = onlinePlayersCache.get(uuid);
} }
if(maybeCachedPacket != null) { if(maybeCachedPacket != null) {
byte[] rewritten = SkinPackets.rewriteUUID(searchUUID, maybeCachedPacket.data); eaglerHandler.sendEaglerMessage(maybeCachedPacket.data.get(eaglerHandler.getEaglerProtocol(),
synchronized(onlinePlayersCache) { searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
onlinePlayersCache.put(searchUUID, maybeCachedPacket);
}
sender.sendData(SkinService.CHANNEL, rewritten);
return; return;
} }
} }
} }
if(skinURL.startsWith("eagler://")) { // customs skulls from exported singleplayer worlds
eaglerHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
return;
}
if(eaglerHandler.skinTextureDownloadRateLimiter.rateLimit(config.getSkinRateLimitPlayer()) && !isLimitedAsAntagonist(sender.getUniqueId())) { if(eaglerHandler.skinTextureDownloadRateLimiter.rateLimit(config.getSkinRateLimitPlayer()) && !isLimitedAsAntagonist(sender.getUniqueId())) {
doAsync(() -> {
processResolveURLTextureForForeign(sender, searchUUID, searchUUID, skinURL, -1); processResolveURLTextureForForeign(sender, searchUUID, searchUUID, skinURL, -1);
});
} }
} }
} }
@ -463,29 +484,37 @@ public class SkinService implements ISkinService {
skin = onlinePlayersCache.get(onlineCacheUUID); skin = onlinePlayersCache.get(onlineCacheUUID);
} }
if(skin != null) { if(skin != null) {
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler)initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
} }
}); });
} }
}else { }else {
PendingTextureDownload newTask = new PendingTextureDownload( PendingTextureDownload newTask = new PendingTextureDownload(skinUUID, urlStr, initiator.getUniqueId(),
skinUUID, urlStr, initiator.getUniqueId(), new Consumer<byte[]>() { new Consumer<byte[]>() {
@Override @Override
public void accept(byte[] t) { public void accept(byte[] t) {
CachedPlayerSkin skin; CachedPlayerSkin skin;
if (t != null) { if (t != null) {
registerTextureToPlayerAssociation(skinUUID, onlineCacheUUID); registerTextureToPlayerAssociation(skinUUID, onlineCacheUUID);
skin = new CachedPlayerSkin(SkinPackets.makeCustomResponse(onlineCacheUUID, modelId, t), skinUUID, modelId); skin = new CachedPlayerSkin(
SkinPacketVersionCache.createCustomV3(
onlineCacheUUID.getMostSignificantBits(),
onlineCacheUUID.getLeastSignificantBits(), modelId, t),
skinUUID, modelId);
} else { } else {
skin = new CachedPlayerSkin(SkinPackets.makePresetResponse(onlineCacheUUID), null, -1); skin = new CachedPlayerSkin(SkinPacketVersionCache.createPreset(
onlineCacheUUID.getMostSignificantBits(),
onlineCacheUUID.getLeastSignificantBits()), null, -1);
} }
synchronized (onlinePlayersCache) { synchronized (onlinePlayersCache) {
onlinePlayersCache.put(onlineCacheUUID, skin); onlinePlayersCache.put(onlineCacheUUID, skin);
} }
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
}, antagonistLogger); }, antagonistLogger);
@ -514,27 +543,38 @@ public class SkinService implements ISkinService {
skin = foreignSkinCache.get(foreignCacheUUID); skin = foreignSkinCache.get(foreignCacheUUID);
} }
if(skin != null) { if(skin != null) {
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
} }
}); });
} }
}else { }else {
PendingTextureDownload newTask = new PendingTextureDownload(skinUUID, urlStr, initiator.getUniqueId(), new Consumer<byte[]>() { PendingTextureDownload newTask = new PendingTextureDownload(skinUUID, urlStr, initiator.getUniqueId(),
new Consumer<byte[]>() {
@Override @Override
public void accept(byte[] t) { public void accept(byte[] t) {
CachedForeignSkin skin; CachedForeignSkin skin;
if (t != null) { if (t != null) {
skin = new CachedForeignSkin(foreignCacheUUID, SkinPackets.makeCustomResponse(foreignCacheUUID, modelId, t), modelId); skin = new CachedForeignSkin(foreignCacheUUID,
SkinPacketVersionCache.createCustomV3(
foreignCacheUUID.getMostSignificantBits(),
foreignCacheUUID.getLeastSignificantBits(), modelId, t),
modelId);
} else { } else {
skin = new CachedForeignSkin(foreignCacheUUID, SkinPackets.makePresetResponse(foreignCacheUUID), -1); skin = new CachedForeignSkin(foreignCacheUUID,
SkinPacketVersionCache.createPreset(
foreignCacheUUID.getMostSignificantBits(),
foreignCacheUUID.getLeastSignificantBits()),
-1);
} }
synchronized (foreignSkinCache) { synchronized (foreignSkinCache) {
foreignSkinCache.put(foreignCacheUUID, skin); foreignSkinCache.put(foreignCacheUUID, skin);
} }
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
}, antagonistLogger); }, antagonistLogger);
@ -563,7 +603,8 @@ public class SkinService implements ISkinService {
skin = onlinePlayersCache.get(playerUUID); skin = onlinePlayersCache.get(playerUUID);
} }
if(skin != null) { if(skin != null) {
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
}else { }else {
processResolveURLTextureForOnline(initiator, playerUUID, t.textureUUID, t.texture, processResolveURLTextureForOnline(initiator, playerUUID, t.textureUUID, t.texture,
@ -582,15 +623,22 @@ public class SkinService implements ISkinService {
if(t == null || t.texture == null) { if(t == null || t.texture == null) {
CachedPlayerSkin skin; CachedPlayerSkin skin;
if (t == null) { if (t == null) {
skin = new CachedPlayerSkin(SkinPackets.makePresetResponse(playerUUID), null, -1); skin = new CachedPlayerSkin(
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits()),
null, -1);
} else { } else {
skin = new CachedPlayerSkin(SkinPackets.makePresetResponse(playerUUID, skin = new CachedPlayerSkin(
SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1); SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
null, -1);
} }
synchronized(onlinePlayersCache) { synchronized(onlinePlayersCache) {
onlinePlayersCache.put(playerUUID, skin); onlinePlayersCache.put(playerUUID, skin);
} }
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
}else { }else {
processResolveURLTextureForOnline(initiator, playerUUID, t.textureUUID, t.texture, processResolveURLTextureForOnline(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model)); SkinPackets.getModelId(t.model));
@ -623,7 +671,8 @@ public class SkinService implements ISkinService {
skin = onlinePlayersCache.get(t.uuid); skin = onlinePlayersCache.get(t.uuid);
} }
if(skin != null) { if(skin != null) {
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
}else { }else {
processResolveURLTextureForOnline(initiator, mapUUID, t.textureUUID, t.texture, processResolveURLTextureForOnline(initiator, mapUUID, t.textureUUID, t.texture,
@ -642,15 +691,20 @@ public class SkinService implements ISkinService {
if(t == null || t.texture == null) { if(t == null || t.texture == null) {
CachedPlayerSkin skin; CachedPlayerSkin skin;
if (t == null) { if (t == null) {
skin = new CachedPlayerSkin(SkinPackets.makePresetResponse(mapUUID), null, -1); skin = new CachedPlayerSkin(
SkinPacketVersionCache.createPreset(mapUUID.getMostSignificantBits(),
mapUUID.getLeastSignificantBits()),
null, -1);
} else { } else {
skin = new CachedPlayerSkin(SkinPackets.makePresetResponse(mapUUID, skin = new CachedPlayerSkin(SkinPacketVersionCache.createPreset(
mapUUID.getMostSignificantBits(), mapUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1); SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1);
} }
synchronized(onlinePlayersCache) { synchronized(onlinePlayersCache) {
onlinePlayersCache.put(mapUUID, skin); onlinePlayersCache.put(mapUUID, skin);
} }
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
}else { }else {
processResolveURLTextureForOnline(initiator, mapUUID, t.textureUUID, t.texture, processResolveURLTextureForOnline(initiator, mapUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model)); SkinPackets.getModelId(t.model));
@ -683,7 +737,8 @@ public class SkinService implements ISkinService {
skin = foreignSkinCache.get(playerUUID); skin = foreignSkinCache.get(playerUUID);
} }
if(skin != null) { if(skin != null) {
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
} }
}else { }else {
processResolveURLTextureForForeign(initiator, playerUUID, t.textureUUID, t.texture, processResolveURLTextureForForeign(initiator, playerUUID, t.textureUUID, t.texture,
@ -702,15 +757,22 @@ public class SkinService implements ISkinService {
if(t == null || t.texture == null) { if(t == null || t.texture == null) {
CachedForeignSkin skin; CachedForeignSkin skin;
if (t == null) { if (t == null) {
skin = new CachedForeignSkin(playerUUID, SkinPackets.makePresetResponse(playerUUID), -1); skin = new CachedForeignSkin(playerUUID,
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits()),
-1);
} else { } else {
skin = new CachedForeignSkin(playerUUID, SkinPackets.makePresetResponse( skin = new CachedForeignSkin(playerUUID,
playerUUID, SkinPackets.getModelId(t.model) == 1 ? 1 : 0), -1); SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
-1);
} }
synchronized(foreignSkinCache) { synchronized(foreignSkinCache) {
foreignSkinCache.put(playerUUID, skin); foreignSkinCache.put(playerUUID, skin);
} }
initiator.sendData(SkinService.CHANNEL, skin.data); EaglerInitialHandler initialHandler = (EaglerInitialHandler) initiator.getPendingConnection();
initialHandler.sendEaglerMessage(skin.data.get(initialHandler.getEaglerProtocol()));
}else { }else {
processResolveURLTextureForForeign(initiator, playerUUID, t.textureUUID, t.texture, processResolveURLTextureForForeign(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model)); SkinPackets.getModelId(t.model));
@ -728,7 +790,7 @@ public class SkinService implements ISkinService {
} }
} }
public void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException { public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
synchronized(foreignSkinCache) { synchronized(foreignSkinCache) {
foreignSkinCache.remove(clientUUID); foreignSkinCache.remove(clientUUID);
} }
@ -796,8 +858,462 @@ public class SkinService implements ISkinService {
} }
} }
public void processForceSkin(UUID playerUUID, EaglerInitialHandler eaglerHandler) {
CachedPlayerSkin maybeCachedPacket;
synchronized(onlinePlayersCache) {
maybeCachedPacket = onlinePlayersCache.get(playerUUID);
}
if(maybeCachedPacket != null) {
eaglerHandler.sendEaglerMessage(maybeCachedPacket.data.getForceClientV4());
}else {
UUID playerTexture;
synchronized(onlinePlayersToTexturesMap) {
playerTexture = onlinePlayersToTexturesMap.get(playerUUID);
}
if(playerTexture != null) {
Collection<UUID> possiblePlayers;
synchronized(onlinePlayersFromTexturesMap) {
possiblePlayers = onlinePlayersFromTexturesMap.get(playerTexture);
}
boolean playersExist = possiblePlayers.size() > 0;
if(playersExist) {
for(UUID uuid : possiblePlayers) {
synchronized(onlinePlayersCache) {
maybeCachedPacket = onlinePlayersCache.get(uuid);
}
if(maybeCachedPacket != null) {
SkinPacketVersionCache rewritten = SkinPacketVersionCache.rewriteUUID(
maybeCachedPacket.data, playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits());
synchronized(onlinePlayersCache) {
onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewritten,
maybeCachedPacket.textureUUID, maybeCachedPacket.modelId));
}
eaglerHandler.sendEaglerMessage(rewritten.getForceClientV4());
return;
}
}
}
CachedForeignSkin foreignSkin;
synchronized(foreignSkinCache) {
foreignSkin = foreignSkinCache.get(playerTexture);
}
if(foreignSkin != null && foreignSkin.modelKnown != -1) {
synchronized(onlinePlayersCache) {
onlinePlayersCache.put(playerUUID,
new CachedPlayerSkin(SkinPacketVersionCache.rewriteUUID(foreignSkin.data,
playerUUID.getMostSignificantBits(), playerUUID.getLeastSignificantBits()),
playerTexture, foreignSkin.modelKnown));
}
synchronized(foreignSkinCache) {
foreignSkinCache.remove(playerTexture);
}
eaglerHandler.sendEaglerMessage(foreignSkin.data.getForceClientV4());
return;
}
}
LoginResult loginProfile = eaglerHandler.getLoginProfile();
if(loginProfile != null) {
Property[] props = loginProfile.getProperties();
if(props.length > 0) {
for(int i = 0; i < props.length; ++i) {
Property pp = props[i];
if(pp.getName().equals("textures")) {
try {
String jsonStr = SkinPackets.bytesToAscii(Base64.decodeBase64(pp.getValue()));
JsonObject json = JsonParser.parseString(jsonStr).getAsJsonObject();
JsonObject skinObj = json.getAsJsonObject("SKIN");
if(skinObj != null) {
JsonElement url = json.get("url");
if(url != null) {
String urlStr = SkinService.sanitizeTextureURL(url.getAsString());
if(urlStr == null) {
break;
}
int model = 0;
JsonElement el = skinObj.get("metadata");
if(el != null && el.isJsonObject()) {
el = el.getAsJsonObject().get("model");
if(el != null) {
model = SkinPackets.getModelId(el.getAsString());
}
}
UUID skinUUID = SkinPackets.createEaglerURLSkinUUID(urlStr);
CachedForeignSkin foreignSkin;
synchronized(foreignSkinCache) {
foreignSkin = foreignSkinCache.remove(skinUUID);
}
if(foreignSkin != null) {
registerTextureToPlayerAssociation(skinUUID, playerUUID);
SkinPacketVersionCache rewrite = SkinPacketVersionCache
.rewriteUUIDModel(foreignSkin.data,
playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits(), model);
synchronized(onlinePlayersCache) {
onlinePlayersCache.put(playerUUID, new CachedPlayerSkin(rewrite, skinUUID, model));
}
eaglerHandler.sendEaglerMessage(rewrite.getForceClientV4());
return;
}
// download player skin, put in onlinePlayersCache, no limit
final int modelf = model;
doAsync(() -> {
processResolveURLTextureForOnlineToForce(eaglerHandler, playerUUID, skinUUID, urlStr, modelf);
});
return;
}
}
}catch(Throwable t) {
}
}
}
}
doAsync(() -> {
if(eaglerHandler.isOnlineMode()) {
processResolveProfileTextureByUUIDForOnlineToForce(eaglerHandler, playerUUID);
}else {
processResolveProfileTextureByNameForOnlineToForce(eaglerHandler, eaglerHandler.getName(), playerUUID);
}
});
}else {
CachedForeignSkin foreignSkin;
synchronized(foreignSkinCache) {
foreignSkin = foreignSkinCache.get(playerUUID);
}
if(foreignSkin != null) {
foreignSkin.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
eaglerHandler.sendEaglerMessage(foreignSkin.data.getForceClientV4());
}else {
if(eaglerHandler.isOnlineMode()) {
doAsync(() -> {
processResolveProfileTextureByUUIDForeignToForce(eaglerHandler, playerUUID);
});
}else {
eaglerHandler.sendEaglerMessage(new SPacketForceClientSkinPresetV4EAG(isAlex(playerUUID) ? 1 : 0));
}
}
}
}
}
private void processResolveURLTextureForOnlineToForce(final EaglerInitialHandler initiator, final UUID onlineCacheUUID,
final UUID skinUUID, final String urlStr, final int modelId) {
synchronized(pendingTextures) {
PendingTextureDownload alreadyPending = pendingTextures.get(skinUUID);
if(alreadyPending != null) {
if(alreadyPending.antagonists.add(initiator.getUniqueId())) {
alreadyPending.callbacks.add(new Consumer<byte[]>() {
@Override
public void accept(byte[] t) {
CachedPlayerSkin skin;
synchronized(onlinePlayersCache) {
skin = onlinePlayersCache.get(onlineCacheUUID);
}
if(skin != null) {
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}
});
}
}else {
PendingTextureDownload newTask = new PendingTextureDownload(skinUUID, urlStr, initiator.getUniqueId(),
new Consumer<byte[]>() {
@Override
public void accept(byte[] t) {
CachedPlayerSkin skin;
if (t != null) {
registerTextureToPlayerAssociation(skinUUID, onlineCacheUUID);
skin = new CachedPlayerSkin(
SkinPacketVersionCache.createCustomV3(
onlineCacheUUID.getMostSignificantBits(),
onlineCacheUUID.getLeastSignificantBits(), modelId, t),
skinUUID, modelId);
} else {
skin = new CachedPlayerSkin(SkinPacketVersionCache.createPreset(
onlineCacheUUID.getMostSignificantBits(),
onlineCacheUUID.getLeastSignificantBits()), null, -1);
}
synchronized (onlinePlayersCache) {
onlinePlayersCache.put(onlineCacheUUID, skin);
}
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}, antagonistLogger);
try {
AsyncSkinProvider.downloadSkin(skinUUID, urlStr, cacheProvider, newTask);
}catch(CancelException ex) {
return;
}
pendingTextures.put(skinUUID, newTask);
}
}
}
private void processResolveURLTextureForForeignToForce(final EaglerInitialHandler initiator, final UUID foreignCacheUUID,
final UUID skinUUID, final String urlStr, final int modelId) {
synchronized(pendingTextures) {
PendingTextureDownload alreadyPending = pendingTextures.get(skinUUID);
if(alreadyPending != null) {
if(alreadyPending.antagonists.add(initiator.getUniqueId())) {
alreadyPending.callbacks.add(new Consumer<byte[]>() {
@Override
public void accept(byte[] t) {
CachedForeignSkin skin;
synchronized(foreignSkinCache) {
skin = foreignSkinCache.get(foreignCacheUUID);
}
if(skin != null) {
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}
});
}
}else {
PendingTextureDownload newTask = new PendingTextureDownload(skinUUID, urlStr, initiator.getUniqueId(),
new Consumer<byte[]>() {
@Override
public void accept(byte[] t) {
CachedForeignSkin skin;
if (t != null) {
skin = new CachedForeignSkin(foreignCacheUUID,
SkinPacketVersionCache.createCustomV3(
foreignCacheUUID.getMostSignificantBits(),
foreignCacheUUID.getLeastSignificantBits(), modelId, t),
modelId);
} else {
skin = new CachedForeignSkin(foreignCacheUUID,
SkinPacketVersionCache.createPreset(
foreignCacheUUID.getMostSignificantBits(),
foreignCacheUUID.getLeastSignificantBits()),
-1);
}
synchronized (foreignSkinCache) {
foreignSkinCache.put(foreignCacheUUID, skin);
}
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}, antagonistLogger);
try {
AsyncSkinProvider.downloadSkin(skinUUID, urlStr, cacheProvider, newTask);
}catch(CancelException ex) {
return;
}
pendingTextures.put(skinUUID, newTask);
}
}
}
private void processResolveProfileTextureByUUIDForOnlineToForce(final EaglerInitialHandler initiator, final UUID playerUUID) {
synchronized(pendingUUIDs) {
PendingProfileUUIDLookup alreadyPending = pendingUUIDs.get(playerUUID);
if(alreadyPending != null) {
if(alreadyPending.antagonists.add(initiator.getUniqueId())) {
alreadyPending.callbacks.add(new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedPlayerSkin skin;
synchronized(onlinePlayersCache) {
skin = onlinePlayersCache.get(playerUUID);
}
if(skin != null) {
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}else {
processResolveURLTextureForOnlineToForce(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
});
}
}else {
PendingProfileUUIDLookup newTask = new PendingProfileUUIDLookup(
playerUUID, initiator.getUniqueId(), new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedPlayerSkin skin;
if (t == null) {
skin = new CachedPlayerSkin(
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits()),
null, -1);
} else {
skin = new CachedPlayerSkin(
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
null, -1);
}
synchronized(onlinePlayersCache) {
onlinePlayersCache.put(playerUUID, skin);
}
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}else {
processResolveURLTextureForOnlineToForce(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
}, antagonistLogger);
try {
AsyncSkinProvider.lookupProfileByUUID(playerUUID, cacheProvider, newTask);
}catch(CancelException ex) {
return;
}
pendingUUIDs.put(playerUUID, newTask);
}
}
}
private void processResolveProfileTextureByNameForOnlineToForce(final EaglerInitialHandler initiator, final String playerName, final UUID mapUUID) {
synchronized(pendingNameLookups) {
PendingProfileNameLookup alreadyPending = pendingNameLookups.get(playerName);
if(alreadyPending != null) {
if(alreadyPending.antagonists.add(initiator.getUniqueId())) {
alreadyPending.callbacks.add(new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedPlayerSkin skin;
synchronized(onlinePlayersCache) {
skin = onlinePlayersCache.get(t.uuid);
}
if(skin != null) {
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}else {
processResolveURLTextureForOnlineToForce(initiator, mapUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
});
}
}else {
PendingProfileNameLookup newTask = new PendingProfileNameLookup(
playerName, initiator.getUniqueId(), new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedPlayerSkin skin;
if (t == null) {
skin = new CachedPlayerSkin(
SkinPacketVersionCache.createPreset(mapUUID.getMostSignificantBits(),
mapUUID.getLeastSignificantBits()),
null, -1);
} else {
skin = new CachedPlayerSkin(SkinPacketVersionCache.createPreset(
mapUUID.getMostSignificantBits(), mapUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0), null, -1);
}
synchronized(onlinePlayersCache) {
onlinePlayersCache.put(mapUUID, skin);
}
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}else {
processResolveURLTextureForOnlineToForce(initiator, mapUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
}, antagonistLogger);
try {
AsyncSkinProvider.lookupProfileByUsername(playerName, cacheProvider, newTask);
}catch(CancelException ex) {
return;
}
pendingNameLookups.put(playerName, newTask);
}
}
}
private void processResolveProfileTextureByUUIDForeignToForce(final EaglerInitialHandler initiator, final UUID playerUUID) {
synchronized(pendingUUIDs) {
PendingProfileUUIDLookup alreadyPending = pendingUUIDs.get(playerUUID);
if(alreadyPending != null) {
if(alreadyPending.antagonists.add(initiator.getUniqueId())) {
alreadyPending.callbacks.add(new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedForeignSkin skin;
synchronized(foreignSkinCache) {
skin = foreignSkinCache.get(playerUUID);
}
if(skin != null) {
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}
}else {
processResolveURLTextureForForeignToForce(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
});
}
}else {
PendingProfileUUIDLookup newTask = new PendingProfileUUIDLookup(
playerUUID, initiator.getUniqueId(), new Consumer<CacheFetchedProfile>() {
@Override
public void accept(CacheFetchedProfile t) {
if(t == null || t.texture == null) {
CachedForeignSkin skin;
if (t == null) {
skin = new CachedForeignSkin(playerUUID,
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits()),
-1);
} else {
skin = new CachedForeignSkin(playerUUID,
SkinPacketVersionCache.createPreset(playerUUID.getMostSignificantBits(),
playerUUID.getLeastSignificantBits(),
SkinPackets.getModelId(t.model) == 1 ? 1 : 0),
-1);
}
synchronized(foreignSkinCache) {
foreignSkinCache.put(playerUUID, skin);
}
initiator.sendEaglerMessage(skin.data.getForceClientV4());
}else {
processResolveURLTextureForForeignToForce(initiator, playerUUID, t.textureUUID, t.texture,
SkinPackets.getModelId(t.model));
}
}
}, antagonistLogger);
try {
AsyncSkinProvider.lookupProfileByUUID(playerUUID, cacheProvider, newTask);
}catch(CancelException ex) {
return;
}
pendingUUIDs.put(playerUUID, newTask);
}
}
}
public void flush() { public void flush() {
long millis = System.currentTimeMillis(); long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
synchronized(foreignSkinCache) { synchronized(foreignSkinCache) {
Iterator<CachedForeignSkin> itr = foreignSkinCache.values().iterator(); Iterator<CachedForeignSkin> itr = foreignSkinCache.values().iterator();
@ -872,6 +1388,14 @@ public class SkinService implements ISkinService {
cacheProvider.flush(); cacheProvider.flush();
} }
public SkinPacketVersionCache getSkin(UUID playerUUID) {
CachedPlayerSkin skin;
synchronized(onlinePlayersCache) {
skin = onlinePlayersCache.get(playerUUID);
}
return skin != null ? skin.data : null;
}
public void shutdown() { public void shutdown() {
resetMaps(); resetMaps();
if(cacheProvider != null) { if(cacheProvider != null) {
@ -916,6 +1440,10 @@ public class SkinService implements ISkinService {
} }
} }
private void doAsync(Runnable handler) {
ProxyServer.getInstance().getScheduler().runAsync(EaglerXBungee.getEagler(), handler);
}
public static String sanitizeTextureURL(String url) { public static String sanitizeTextureURL(String url) {
try { try {
URI uri = URI.create(url); URI uri = URI.create(url);

View File

@ -1,6 +1,5 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -11,6 +10,8 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder; import com.google.common.collect.MultimapBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/** /**
@ -35,16 +36,16 @@ public class SkinServiceOffline implements ISkinService {
private static class CachedSkin { private static class CachedSkin {
protected final UUID uuid; protected final UUID uuid;
protected final byte[] packet; protected final SkinPacketVersionCache packet;
protected CachedSkin(UUID uuid, byte[] packet) { protected CachedSkin(UUID uuid, SkinPacketVersionCache packet) {
this.uuid = uuid; this.uuid = uuid;
this.packet = packet; this.packet = packet;
} }
} }
private final Map<UUID, CachedSkin> skinCache = new HashMap(); private final Map<UUID, CachedSkin> skinCache = new HashMap<>();
private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build(); private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
@ -56,20 +57,23 @@ public class SkinServiceOffline implements ISkinService {
} }
public void processGetOtherSkin(UUID searchUUID, UserConnection sender) { public void processGetOtherSkin(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) { EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
if(initialHandler.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
CachedSkin cached; CachedSkin cached;
synchronized(skinCache) { synchronized(skinCache) {
cached = skinCache.get(searchUUID); cached = skinCache.get(searchUUID);
} }
if(cached != null) { if(cached != null) {
sender.sendData(SkinService.CHANNEL, cached.packet); initialHandler.sendEaglerMessage(cached.packet.get(initialHandler.getEaglerProtocol()));
}else { }else {
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID)); initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
} }
} }
} }
public void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender) { public void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender) {
EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
Collection<UUID> uuids; Collection<UUID> uuids;
synchronized(onlinePlayersFromTexturesMap) { synchronized(onlinePlayersFromTexturesMap) {
uuids = onlinePlayersFromTexturesMap.get(searchUUID); uuids = onlinePlayersFromTexturesMap.get(searchUUID);
@ -81,15 +85,23 @@ public class SkinServiceOffline implements ISkinService {
while(uuidItr.hasNext()) { while(uuidItr.hasNext()) {
cached = skinCache.get(uuidItr.next()); cached = skinCache.get(uuidItr.next());
if(cached != null) { if(cached != null) {
sender.sendData(SkinService.CHANNEL, SkinPackets.rewriteUUID(searchUUID, cached.packet)); initialHandler.sendEaglerMessage(cached.packet.get(initialHandler.getEaglerProtocol(),
searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
return;
} }
} }
} }
} }
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID)); if(skinURL.startsWith("eagler://")) { // customs skulls from exported singleplayer worlds
initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
return;
}
initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
} }
public void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException { public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
synchronized(skinCache) { synchronized(skinCache) {
skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket)); skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
} }
@ -107,6 +119,16 @@ public class SkinServiceOffline implements ISkinService {
} }
} }
public void processForceSkin(UUID playerUUID, EaglerInitialHandler initialHandler) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(playerUUID);
}
if(cached != null) {
initialHandler.sendEaglerMessage(cached.packet.getForceClientV4());
}
}
public void flush() { public void flush() {
// no // no
} }
@ -117,4 +139,12 @@ public class SkinServiceOffline implements ISkinService {
} }
} }
public SkinPacketVersionCache getSkin(UUID playerUUID) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(playerUUID);
}
return cached != null ? cached.packet : null;
}
} }

View File

@ -66,7 +66,7 @@ public class EaglerDrivers {
driversJARs.put(address, classLoader); driversJARs.put(address, classLoader);
} }
Class loadedDriver; Class<?> loadedDriver;
try { try {
loadedDriver = classLoader.loadClass(driverClass); loadedDriver = classLoader.loadClass(driverClass);
}catch(ClassNotFoundException ex) { }catch(ClassNotFoundException ex) {
@ -92,8 +92,8 @@ public class EaglerDrivers {
return sqlDriver; return sqlDriver;
} }
private static final Map<String, URLClassLoader> driversJARs = new HashMap(); private static final Map<String, URLClassLoader> driversJARs = new HashMap<>();
private static final Map<String, Driver> driversDrivers = new HashMap(); private static final Map<String, Driver> driversDrivers = new HashMap<>();
public static Connection connectToDatabase(String address, String driverClass, String driverPath, Properties props) public static Connection connectToDatabase(String address, String driverClass, String driverPath, Properties props)
throws SQLException { throws SQLException {

View File

@ -5,6 +5,8 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/** /**
* Copyright (c) 2022 ayunami2000. All Rights Reserved. * Copyright (c) 2022 ayunami2000. All Rights Reserved.
* *
@ -42,7 +44,7 @@ public class ExpiringSet<T> extends HashSet<T> {
public void checkForExpirations() { public void checkForExpirations() {
Iterator<T> iterator = this.timestamps.keySet().iterator(); Iterator<T> iterator = this.timestamps.keySet().iterator();
long now = System.currentTimeMillis(); long now = EaglerXBungeeAPIHelper.steadyTimeMillis();
while (iterator.hasNext()) { while (iterator.hasNext()) {
T element = iterator.next(); T element = iterator.next();
if (super.contains(element)) { if (super.contains(element)) {
@ -61,7 +63,7 @@ public class ExpiringSet<T> extends HashSet<T> {
public boolean add(T o) { public boolean add(T o) {
checkForExpirations(); checkForExpirations();
boolean success = super.add(o); boolean success = super.add(o);
if (success) timestamps.put(o, System.currentTimeMillis()); if (success) timestamps.put(o, EaglerXBungeeAPIHelper.steadyTimeMillis());
return success; return success;
} }

View File

@ -1,5 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice; package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -7,7 +9,13 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
@ -29,7 +37,7 @@ import net.md_5.bungee.api.config.ServerInfo;
public class VoiceServerImpl { public class VoiceServerImpl {
private final ServerInfo server; private final ServerInfo server;
private final byte[] iceServersPacket; private final GameMessagePacket iceServersPacket;
private final Map<UUID, UserConnection> voicePlayers = new HashMap<>(); private final Map<UUID, UserConnection> voicePlayers = new HashMap<>();
private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>(); private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>();
@ -70,17 +78,23 @@ public class VoiceServerImpl {
} }
} }
VoiceServerImpl(ServerInfo server, byte[] iceServersPacket) { VoiceServerImpl(ServerInfo server, GameMessagePacket iceServersPacket) {
this.server = server; this.server = server;
this.iceServersPacket = iceServersPacket; this.iceServersPacket = iceServersPacket;
} }
public void handlePlayerLoggedIn(UserConnection player) { public void handlePlayerLoggedIn(UserConnection player) {
player.sendData(VoiceService.CHANNEL, iceServersPacket); EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
eaglerHandler.sendEaglerMessage(iceServersPacket);
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
} }
public void handlePlayerLoggedOut(UserConnection player) { public void handlePlayerLoggedOut(UserConnection player) {
removeUser(player.getUniqueId()); EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
removeUser(eaglerHandler.getUniqueId());
} }
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) { void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
@ -115,63 +129,93 @@ public class VoiceServerImpl {
voiceRequests.remove(senderUUID); voiceRequests.remove(senderUUID);
// send each other add data // send each other add data
voicePairs.add(newPair); voicePairs.add(newPair);
targetPlayerCon.sendData(VoiceService.CHANNEL, EaglerInitialHandler targetInitialHandler = (EaglerInitialHandler) targetPlayerCon
VoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false)); .getPendingConnection();
sender.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketConnect(player, true)); if (targetInitialHandler.getEaglerProtocol().ver <= 3) {
targetInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false, false));
} else {
targetInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false));
}
EaglerInitialHandler senderInitialHandler = (EaglerInitialHandler) sender.getPendingConnection();
if (senderInitialHandler.getEaglerProtocol().ver <= 3) {
senderInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), false, true));
} else {
senderInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), true));
}
} }
} }
} }
void handleVoiceSignalPacketTypeConnect(UserConnection sender) { void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(!((EaglerInitialHandler)sender.getPendingConnection()).voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) { EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)sender.getPendingConnection();
if(!eaglerHandler.voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
return; return;
} }
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.ENABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_ENABLED);
}
synchronized (voicePlayers) { synchronized (voicePlayers) {
if (voicePlayers.containsKey(sender.getUniqueId())) { if (voicePlayers.containsKey(eaglerHandler.getUniqueId())) {
return; return;
} }
boolean hasNoOtherPlayers = voicePlayers.isEmpty(); boolean hasNoOtherPlayers = voicePlayers.isEmpty();
voicePlayers.put(sender.getUniqueId(), sender); voicePlayers.put(eaglerHandler.getUniqueId(), sender);
if (hasNoOtherPlayers) { if (hasNoOtherPlayers) {
return; return;
} }
byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(UserConnection userCon : voicePlayers.values()) { for(UserConnection userCon : voicePlayers.values()) {
userCon.sendData(VoiceService.CHANNEL, packetToBroadcast); UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getDisplayName()));
}
GameMessagePacket packetToBroadcast = new SPacketVoiceSignalGlobalEAG(userDatas);
for (UserConnection userCon : voicePlayers.values()) {
((EaglerInitialHandler)userCon.getPendingConnection()).sendEaglerMessage(packetToBroadcast);
} }
} }
} }
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) { void handleVoiceSignalPacketTypeICE(UUID player, byte[] str, UserConnection sender) {
UserConnection pass; UserConnection pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId()); VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) { synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
} }
if (pass != null) { if (pass != null) {
pass.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueId(), str)); UUID uuid = sender.getUniqueId();
((EaglerInitialHandler) pass.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalICEEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), str));
} }
} }
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) { void handleVoiceSignalPacketTypeDesc(UUID player, byte[] str, UserConnection sender) {
UserConnection pass; UserConnection pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId()); VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) { synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
} }
if (pass != null) { if (pass != null) {
pass.sendData(VoiceService.CHANNEL, UUID uuid = sender.getUniqueId();
VoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueId(), str)); ((EaglerInitialHandler) pass.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalDescEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), str));
} }
} }
void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) { void handleVoiceSignalPacketTypeDisconnect(UserConnection sender) {
if (player != null) { removeUser(sender.getUniqueId());
}
void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, UserConnection sender) {
synchronized (voicePlayers) { synchronized (voicePlayers) {
if (!voicePlayers.containsKey(player)) { if (!voicePlayers.containsKey(player)) {
return; return;
} }
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator(); Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) { while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next(); VoicePair voicePair = pairsItr.next();
@ -185,36 +229,45 @@ public class VoiceServerImpl {
pairsItr.remove(); pairsItr.remove();
UserConnection conn = voicePlayers.get(target); UserConnection conn = voicePlayers.get(target);
if (conn != null) { if (conn != null) {
if (userDisconnectPacket == null) { ((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(player); new SPacketVoiceSignalDisconnectPeerEAG(player.getMostSignificantBits(),
player.getLeastSignificantBits()));
} }
conn.sendData(VoiceService.CHANNEL, userDisconnectPacket); ((EaglerInitialHandler) sender.getPendingConnection())
} .sendEaglerMessage(new SPacketVoiceSignalDisconnectPeerEAG(target.getMostSignificantBits(),
sender.sendData(VoiceService.CHANNEL, target.getLeastSignificantBits()));
VoiceSignalPackets.makeVoiceSignalPacketDisconnect(target));
} }
} }
} }
} else {
removeUser(sender.getUniqueId());
}
} }
public void removeUser(UUID user) { void removeUser(UUID user) {
synchronized (voicePlayers) { synchronized (voicePlayers) {
if (voicePlayers.remove(user) == null) { UserConnection connRemove;
if ((connRemove = voicePlayers.remove(user)) == null) {
return; return;
}else {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)connRemove.getPendingConnection();
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
} }
voiceRequests.remove(user); voiceRequests.remove(user);
if (voicePlayers.size() > 0) { if (voicePlayers.size() > 0) {
byte[] voicePlayersPkt = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(UserConnection userCon : voicePlayers.values()) {
UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getDisplayName()));
}
GameMessagePacket voicePlayersPkt = new SPacketVoiceSignalGlobalEAG(userDatas);
for (UserConnection userCon : voicePlayers.values()) { for (UserConnection userCon : voicePlayers.values()) {
if (!user.equals(userCon.getUniqueId())) { if (!user.equals(userCon.getUniqueId())) {
userCon.sendData(VoiceService.CHANNEL, voicePlayersPkt); ((EaglerInitialHandler)userCon.getPendingConnection()).sendEaglerMessage(voicePlayersPkt);
} }
} }
} }
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator(); Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) { while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next(); VoicePair voicePair = pairsItr.next();
@ -229,10 +282,9 @@ public class VoiceServerImpl {
if (voicePlayers.size() > 0) { if (voicePlayers.size() > 0) {
UserConnection conn = voicePlayers.get(target); UserConnection conn = voicePlayers.get(target);
if (conn != null) { if (conn != null) {
if (userDisconnectPacket == null) { ((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user); new SPacketVoiceSignalDisconnectPeerEAG(user.getMostSignificantBits(),
} user.getLeastSignificantBits()));
conn.sendData(VoiceService.CHANNEL, userDisconnectPacket);
} }
} }
} }
@ -240,4 +292,13 @@ public class VoiceServerImpl {
} }
} }
EnumVoiceState getPlayerVoiceState(UUID uniqueId) {
synchronized (voicePlayers) {
if(voicePlayers.containsKey(uniqueId)) {
return EnumVoiceState.ENABLED;
}
}
return EnumVoiceState.DISABLED;
}
} }

View File

@ -7,7 +7,14 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import gnu.trove.map.TMap; import gnu.trove.map.TMap;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalAllowedEAG;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
@ -29,17 +36,15 @@ import net.md_5.bungee.api.config.ServerInfo;
*/ */
public class VoiceService { public class VoiceService {
public static final String CHANNEL = "EAG|Voice-1.8"; private final Map<String, VoiceServerImpl> serverMap = new HashMap<>();
private final GameMessagePacket disableVoicePacket;
private final Map<String, VoiceServerImpl> serverMap = new HashMap();
private final byte[] disableVoicePacket;
public VoiceService(EaglerBungeeConfig conf) { public VoiceService(EaglerBungeeConfig conf) {
this.disableVoicePacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(false, null); this.disableVoicePacket = new SPacketVoiceSignalAllowedEAG(false, null);
String[] iceServers = conf.getICEServers().toArray(new String[conf.getICEServers().size()]); String[] iceServers = conf.getICEServers().toArray(new String[conf.getICEServers().size()]);
byte[] iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers); SPacketVoiceSignalAllowedEAG iceServersPacket = new SPacketVoiceSignalAllowedEAG(true, iceServers);
TMap<String,ServerInfo> servers = BungeeCord.getInstance().config.getServers(); TMap<String,ServerInfo> servers = BungeeCord.getInstance().config.getServers();
Set<String> keySet = new HashSet(servers.keySet()); Set<String> keySet = new HashSet<>(servers.keySet());
keySet.removeAll(conf.getDisableVoiceOnServersSet()); keySet.removeAll(conf.getDisableVoiceOnServersSet());
for(String s : keySet) { for(String s : keySet) {
serverMap.put(s, new VoiceServerImpl(servers.get(s), iceServersPacket)); serverMap.put(s, new VoiceServerImpl(servers.get(s), iceServersPacket));
@ -59,7 +64,12 @@ public class VoiceService {
if(svr != null) { if(svr != null) {
svr.handlePlayerLoggedIn(player); svr.handlePlayerLoggedIn(player);
}else { }else {
player.sendData(CHANNEL, disableVoicePacket); EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
eaglerHandler.sendEaglerMessage(disableVoicePacket);
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
}
} }
} }
@ -70,7 +80,7 @@ public class VoiceService {
} }
} }
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) { public void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
if(sender.getServer() != null) { if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) { if(svr != null) {
@ -79,7 +89,7 @@ public class VoiceService {
} }
} }
void handleVoiceSignalPacketTypeConnect(UserConnection sender) { public void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(sender.getServer() != null) { if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) { if(svr != null) {
@ -88,7 +98,7 @@ public class VoiceService {
} }
} }
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) { public void handleVoiceSignalPacketTypeICE(UUID player, byte[] str, UserConnection sender) {
if(sender.getServer() != null) { if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) { if(svr != null) {
@ -97,7 +107,7 @@ public class VoiceService {
} }
} }
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) { public void handleVoiceSignalPacketTypeDesc(UUID player, byte[] str, UserConnection sender) {
if(sender.getServer() != null) { if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) { if(svr != null) {
@ -106,13 +116,31 @@ public class VoiceService {
} }
} }
void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) { public void handleVoiceSignalPacketTypeDisconnect(UserConnection sender) {
if(sender.getServer() != null) { if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) { if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnect(player, sender); svr.handleVoiceSignalPacketTypeDisconnect(sender);
} }
} }
} }
public void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnectPeer(player, sender);
}
}
}
public EnumVoiceState getPlayerVoiceState(UUID player, ServerInfo info) {
VoiceServerImpl svr = serverMap.get(info.getName());
if(svr != null) {
return svr.getPlayerVoiceState(player);
}else {
return EnumVoiceState.SERVER_DISABLE;
}
}
} }

View File

@ -1,194 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.UUID;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.DefinedPacket;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class VoiceSignalPackets {
static final int VOICE_SIGNAL_ALLOWED = 0;
static final int VOICE_SIGNAL_REQUEST = 0;
static final int VOICE_SIGNAL_CONNECT = 1;
static final int VOICE_SIGNAL_DISCONNECT = 2;
static final int VOICE_SIGNAL_ICE = 3;
static final int VOICE_SIGNAL_DESC = 4;
static final int VOICE_SIGNAL_GLOBAL = 5;
public static void processPacket(byte[] data, UserConnection sender, VoiceService voiceService) throws IOException {
int packetId = -1;
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
try {
ByteBuf buffer = Unpooled.wrappedBuffer(data).writerIndex(data.length);
packetId = buffer.readUnsignedByte();
switch(packetId) {
case VOICE_SIGNAL_REQUEST: {
voiceService.handleVoiceSignalPacketTypeRequest(DefinedPacket.readUUID(buffer), sender);
break;
}
case VOICE_SIGNAL_CONNECT: {
voiceService.handleVoiceSignalPacketTypeConnect(sender);
break;
}
case VOICE_SIGNAL_ICE: {
voiceService.handleVoiceSignalPacketTypeICE(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DESC: {
voiceService.handleVoiceSignalPacketTypeDesc(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DISCONNECT: {
voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? DefinedPacket.readUUID(buffer) : null, sender);
break;
}
default: {
throw new IOException("Unknown packet type " + packetId);
}
}
if(buffer.readableBytes() > 0) {
throw new IOException("Voice packet is too long!");
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling voice packet type " + packetId, t);
}
}
static byte[] makeVoiceSignalPacketAllowed(boolean allowed, String[] iceServers) {
if (iceServers == null) {
byte[] ret = new byte[2];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
return ret;
}
byte[][] iceServersBytes = new byte[iceServers.length][];
int totalLen = 2 + getVarIntSize(iceServers.length);
for(int i = 0; i < iceServers.length; ++i) {
byte[] b = iceServersBytes[i] = iceServers[i].getBytes(StandardCharsets.UTF_8);
totalLen += getVarIntSize(b.length) + b.length;
}
byte[] ret = new byte[totalLen];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
DefinedPacket.writeVarInt(iceServersBytes.length, wrappedBuffer);
for(int i = 0; i < iceServersBytes.length; ++i) {
byte[] b = iceServersBytes[i];
DefinedPacket.writeVarInt(b.length, wrappedBuffer);
wrappedBuffer.writeBytes(b);
}
return ret;
}
static byte[] makeVoiceSignalPacketGlobal(Collection<UserConnection> users) {
int cnt = users.size();
byte[][] displayNames = new byte[cnt][];
int i = 0;
for(UserConnection user : users) {
String name = user.getDisplayName();
if(name.length() > 16) name = name.substring(0, 16);
displayNames[i++] = name.getBytes(StandardCharsets.UTF_8);
}
int totalLength = 1 + getVarIntSize(cnt) + (cnt << 4);
for(i = 0; i < cnt; ++i) {
totalLength += getVarIntSize(displayNames[i].length) + displayNames[i].length;
}
byte[] ret = new byte[totalLength];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_GLOBAL);
DefinedPacket.writeVarInt(cnt, wrappedBuffer);
for(UserConnection user : users) {
DefinedPacket.writeUUID(user.getUniqueId(), wrappedBuffer);
}
for(i = 0; i < cnt; ++i) {
DefinedPacket.writeVarInt(displayNames[i].length, wrappedBuffer);
wrappedBuffer.writeBytes(displayNames[i]);
}
return ret;
}
static byte[] makeVoiceSignalPacketConnect(UUID player, boolean offer) {
byte[] ret = new byte[18];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
wrappedBuffer.writeBoolean(offer);
return ret;
}
static byte[] makeVoiceSignalPacketConnectAnnounce(UUID player) {
byte[] ret = new byte[17];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
return ret;
}
static byte[] makeVoiceSignalPacketDisconnect(UUID player) {
if(player == null) {
return new byte[] { (byte)VOICE_SIGNAL_DISCONNECT };
}
byte[] ret = new byte[17];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
return ret;
}
static byte[] makeVoiceSignalPacketICE(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ICE);
DefinedPacket.writeUUID(player, wrappedBuffer);
DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
static byte[] makeVoiceSignalPacketDesc(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_DESC);
DefinedPacket.writeUUID(player, wrappedBuffer);
DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
public static int getVarIntSize(int input) {
for (int i = 1; i < 5; ++i) {
if ((input & -1 << i * 7) == 0) {
return i;
}
}
return 5;
}
}

View File

@ -1,11 +1,11 @@
voice_stun_servers: voice_servers_no_passwd:
- 'stun:stun.l.google.com:19302' - 'stun:stun.l.google.com:19302'
- 'stun:stun1.l.google.com:19302' - 'stun:stun1.l.google.com:19302'
- 'stun:stun2.l.google.com:19302' - 'stun:stun2.l.google.com:19302'
- 'stun:stun3.l.google.com:19302' - 'stun:stun3.l.google.com:19302'
- 'stun:stun4.l.google.com:19302' - 'stun:stun4.l.google.com:19302'
- 'stun:openrelay.metered.ca:80' - 'stun:openrelay.metered.ca:80'
voice_turn_servers: voice_servers_passwd:
openrelay1: openrelay1:
url: 'turn:openrelay.metered.ca:80' url: 'turn:openrelay.metered.ca:80'
username: 'openrelayproject' username: 'openrelayproject'

View File

@ -13,6 +13,10 @@ listener_01:
- '&6An EaglercraftX server' - '&6An EaglercraftX server'
allow_motd: true allow_motd: true
allow_query: true allow_query: true
allow_protocol_v3: true
allow_protocol_v4: true
protocol_v4_defrag_send_delay: 10
allow_cookie_revoke_query: true
request_motd_cache: request_motd_cache:
cache_ttl: 7200 cache_ttl: 7200
online_server_list_animation: false online_server_list_animation: false

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eaglercraft Server</title>
<style type="text/css">
body {
margin: 16px;
font-family: sans-serif;
}
</style>
</head>
<script type="text/javascript">
{% embed text `message_api_v1.js` %}
</script>
<script type="text/javascript">
// Open the channel, this can be any string
serverMessageAPI.openChannel("com.example.test_channel");
// Set the callback for when messages are recieved
serverMessageAPI.addEventListener("message", function(msg) {
var newElement = document.createElement("li");
if(msg.type === "binary") {
newElement.innerText = "[" + msg.channel + "][binary] ArrayBuffer(" + msg.data.byteLength + ")";
}else if(msg.type === "string") {
newElement.innerText = "[" + msg.channel + "][string] \"" + msg.data + "\"";
}
document.getElementById("messages_recieved").appendChild(newElement);
});
window.addEventListener("load", function() {
document.getElementById("message_send").addEventListener("click", function() {
var el = document.getElementById("message_contents");
var toSend = el.value.trim();
if(toSend.length > 0) {
// Send the message, can be a string, ArrayBuffer, Int8Array, or Uint8Array
serverMessageAPI.send("com.example.test_channel", toSend);
el.value = "";
}
});
});
/*
* // Add this event listener to your bungee plugin:
*
* @EventHandler
* public void testWebViewMessageAPI(EaglercraftWebViewMessageEvent event) {
* if(event.getType() == MessageType.STRING && event.getChannelName().equals("com.example.test_channel")) {
* event.sendResponse(event.getAsString());
* }
* }
*
*/
</script>
<body>
<h1>Message API Test</h1>
<h4>Server Version: {% global `plugin_name` %} {% global `plugin_version` %}</h4>
<h4>Make sure you enable javascript in "pause_menu.yml"</h4>
<p>Message: <input type="text" id="message_contents" placeholder="eagler"> <button id="message_send">Send</button></p>
<p>Recieved from server:</p>
<ul id="messages_recieved"></ul>
</body>
</html>

View File

@ -0,0 +1,63 @@
"use strict";
window.serverMessageAPI = (function() {
var channelOpen = null;
var messageHandlers = [];
window.addEventListener("message", function(evt) {
var dat = evt.data;
if((typeof dat === "object") && dat.ver === 1 && (typeof dat.type === "string") && (typeof dat.channel === "string") && dat.channel.length > 0) {
for(var i = 0; i < messageHandlers.length; ++i) {
messageHandlers[i](dat);
}
}
});
var ServerMessageAPIError = function(message) {
this.name = "ServerMessageAPIError";
this.message = message;
};
ServerMessageAPIError.prototype = Error.prototype;
var openCh = function(chName) {
if(channelOpen !== null) throw new ServerMessageAPIError("Cannot open multiple channels, this feature is not supported!");
channelOpen = chName;
window.parent.postMessage({ver:1,channel:chName,open:true}, "*");
};
var closeCh = function(chName) {
if(channelOpen !== chName) throw new ServerMessageAPIError("Cannot close channel \"" + chName + "\", that channel is not open!");
channelOpen = null;
window.parent.postMessage({ver:1,channel:chName,open:false}, "*");
};
var addListener = function(name, handler) {
if(name === "message") messageHandlers.push(handler);
};
var remListener = function(name, handler) {
if(name === "message") messageHandlers = messageHandlers.filter(function(o) { return o !== handler; });
};
var fixTypedArray = function(arr) {
if(arr.length === arr.buffer.byteLength) {
return arr.buffer;
}else {
var toSend = (data instanceof Uint8Array) ? new Uint8Array(arr.length) : new Int8Array(arr.length);
toSend.set(arr);
return toSend.buffer;
}
};
var send = function(chName, data) {
if(channelOpen !== chName) throw new ServerMessageAPIError("Cannot send message on channel \"" + chName + "\", that channel is not open!");
if(typeof data === "string") {
window.parent.postMessage({ver:1,channel:chName,data:data}, "*");
}else if(data instanceof ArrayBuffer) {
window.parent.postMessage({ver:1,channel:chName,data:data}, "*");
}else if((data instanceof Uint8Array) || (data instanceof Int8Array)) {
window.parent.postMessage({ver:1,channel:chName,data:fixTypedArray(data)}, "*");
}else {
throw new ServerMessageAPIError("Only strings, ArrayBuffers, Uint8Arrays, and Int8Arrays can be sent with this function!");
}
};
return {
ServerMessageAPIError: ServerMessageAPIError,
openChannel: openCh,
closeChannel: closeCh,
addEventListener: addListener,
removeEventListener: remListener,
send: send
};
})();

View File

@ -0,0 +1,43 @@
enable_custom_pause_menu: false
server_info_button:
enable_button: true
button_text: 'Server Info'
button_mode_open_new_tab: false
server_info_embed_url: ''
button_mode_embed_file: true
server_info_embed_file: 'server_info.html'
server_info_embed_screen_title: 'Server Info'
server_info_embed_send_chunk_rate: 1
server_info_embed_send_chunk_size: 24576
enable_template_macros: true
server_info_embed_template_globals:
example_global: 'eagler'
allow_embed_template_eval_macro: false
enable_webview_javascript: false
enable_webview_message_api: false
enable_webview_strict_csp: true
discord_button:
enable_button: true
button_text: 'Discord'
button_url: 'https://invite url here'
custom_images:
icon_title_L: ''
icon_title_R: ''
icon_backToGame_L: ''
icon_backToGame_R: ''
icon_achievements_L: ''
icon_achievements_R: ''
icon_statistics_L: ''
icon_statistics_R: ''
icon_serverInfo_L: ''
icon_serverInfo_R: ''
icon_options_L: ''
icon_options_R: ''
icon_discord_L: ''
icon_discord_R: ''
icon_disconnect_L: ''
icon_disconnect_R: ''
icon_background_pause: 'test_image.png'
icon_background_all: 'test_image.png'
icon_watermark_pause: ''
icon_watermark_all: ''

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eaglercraft Server</title>
<style type="text/css">
body {
margin: 16px;
font-family: sans-serif;
}
</style>
</head>
<body>
<h1>Hello World</h1>
{% htmlescape on %}
<p>Server Name: {% global `server_name` %}</p>
<p>Using: {% global `plugin_name` %} {% global `plugin_version` %}</p>
<p>JVM: {% property `java.vm.name` `(unknown)` %} ({% property `java.vm.info` `null` %}) {% property `java.vm.vendor` `(unknown)` %}</p>
{% htmlescape off %}
<p><img src="data:image/png;base64,{% embed base64 `test_image.png` %}" /></p>
<!-- Note: JPEGs are recommended for larger images to reduce their size -->
<!-- <p><img src="data:image/jpeg;base64,(% embed base64 `large_image.jpg` %)" /></p> -->
</body>
</html>

View File

@ -24,3 +24,4 @@ enable_is_eagler_player_property: true
disable_voice_chat_on_servers: [] disable_voice_chat_on_servers: []
disable_fnaw_skins_everywhere: false disable_fnaw_skins_everywhere: false
disable_fnaw_skins_on_servers: [] disable_fnaw_skins_on_servers: []
enable_backend_rpc_api: false

View File

@ -1,5 +1,5 @@
name: EaglercraftXBungee name: EaglercraftXBungee
main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee
version: 1.2.7 version: 1.3.0
description: Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks description: Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks
author: lax1dude author: lax1dude

View File

@ -1 +1 @@
1.2.7 1.3.0