Compare commits

..

131 Commits

Author SHA1 Message Date
babb22bf88 Remove duplicate mapping for MINECRAFT_1_21_2 in StateRegistry
All checks were successful
SteamWarCI Build successful
2025-11-09 01:51:08 +01:00
468127996c Merge remote-tracking branch 'upstream/dev/3.0.0'
Some checks failed
SteamWarCI Build failed
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2025-11-09 01:46:27 +01:00
Adrian
b6b6b20fe9 Generate a new forwarding secret file if the file is deleted (#1671)
* Generate a new forwarding secret file if the file is deleted

This allows to generate a new forwarding secret simply by deleting the file if required.
The file will only be generated if the forwarding secret is not configured through a system property

resolves #1670

* Add file creation message
2025-10-23 11:13:36 -05:00
Dylan Sperrer
f75b512837 Moved pre-1.19.1 command argument validation so it prints the faulty identifier (#1675) 2025-10-21 12:45:04 -05:00
Adrian
7412aca81c Fixed sending ServerData packets if the description component from the backend server is null (#1673) 2025-10-20 19:51:47 -05:00
Adrian
02cf349075 Fixed disconnecting players in the middle of a backend server reconfiguration (#1669) 2025-10-19 09:43:40 -05:00
Andrew Steinborn
67b988e6d2 Update all localizations to use the current year 2025-10-18 18:58:21 -04:00
Andrew Steinborn
d2c13c2a4c Provide encode buffer hint 2025-10-18 17:36:39 -04:00
Andrew Steinborn
498a38cf74 Re-enable adaptive allocator
Recent Netty versions have improved the adaptive allocator, and we shouldn't be seeing the OOM issues others were noticing before. Let's re-enable it.

As for the buffer resizing issue, the upstream issue netty/netty#14912 is long fixed. I think we *should* pre-allocate the buffers beforehand much more aggressively, but that has to be future work.
2025-10-18 16:40:54 -04:00
Andrew Steinborn
13a1c93ea6 Bump Netty to 4.2.7.Final 2025-10-18 16:22:32 -04:00
Shane Freeder
38a0a7ed27 use correct string length for newer MC versions (Fixes #1629) (#1668) 2025-10-18 21:22:26 +01:00
Andrew Steinborn
70c3eabdb1 Minor optimizations for MinecraftCompressorAndLengthEncoder and friends
No need to bounce around changing the writer index, we can just set the value directly.

Also pull out the handshake checks into a separate function, to improve inlining.
2025-10-18 16:15:22 -04:00
okx-code
4cd3b68697 Fix players disconnecting when updating boss bars (#1656)
* Fix 1.20.2+ clients disconnecting when updating boss bars

On 1.20.2, the Minecraft client started clearing boss bars after the login packet, which meant that the ProxyServer#showBossbar API would result in the player getting kicked if the boss bar they were previously shown was updated after switching servers.

Therefore, I have added BossBarManager which drops boss bar packets once the client enters the configure phase to ensure that they do not disconnect, and then re-adds the boss bar once the client enters the login phase.

This ensures that clients do not receive boss bar updates for boss bars that they don't exist and causing them to disconnect. I have also taken care to ensure that this logic only applies on 1.20.2 and up, as it is not necessary for older clients.

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-10-16 23:12:57 -05:00
Emil
1140fc65ba fix: Enable EMIT_CLICK_URL_HTTPS on component serializers (#1665) 2025-10-14 13:10:57 -05:00
Ross
5753548b44 Fix SimpleCommand suggestion offset (#1664)
* Fix command suggestion offset

* fix length error

* add test

* checkstyle

---------

Co-authored-by: Ross <2086824-trashp@users.noreply.gitlab.com>
2025-10-13 14:41:33 -05:00
Ross
806b386cdb Fix command suggestion offset (#1662) 2025-10-11 21:11:44 -05:00
Cedric
d266059abe Update adventure to version 4.25.0 (#1660) 2025-10-10 03:40:10 -05:00
Aaron
b1dd26fbc4 1.21.10 (#1658) 2025-10-07 15:40:25 +01:00
Timon Seidel
c8c27af7c3 feat: Add primitive support for sound api (#1422)
* feat: Add primitive support for sound api

* change to fail silently

fix: implement the correct playSound method
fix: bumped "since" version

* chore: update 1.21.5

* chore: enforce adventure's policy of not throwing exceptions on unsupported actions

* feat: allow sounds to be played from other players (on the same server)

* chore(fix): add missing getters/setters in packets

* chore: update 1.21.6
chore: added own notes to playSound method, as adventure moved them to the Sound class

* chore: cleanup

* fix: ignore invalid sound source
fix: sound source error on wrong version

* chore: prettify key writing

* Implement missing Player#playSound(Sound)

* Reverted Player#playSound(Sound) implementation

Also, improved documentation related to #playSound mehtods

* chore(jd): mark dialog operations unsupported

* chore: update 1.21.9

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-29 09:22:19 -05:00
Aaron
ba01492790 Minecraft 1.21.9 (#1651)
* 1.21.9-pr1
- not tested yet

* 1.21.9-pre2

* feat: forward code of conduct packets in CONFIG state

* 1.21.9

---------

Co-authored-by: Emilxyz <12966472+Emilxyz@users.noreply.github.com>
2025-09-27 12:15:02 -05:00
Adrian Gonzales
94368d5021 Update publishing endpoint 2025-09-25 19:28:54 -05:00
Joo200
ec793a9fdb Log console command executions (#1137) 2025-09-24 00:50:05 -05:00
DartCZ
37f622f226 feat: add ProxyPreShutdownEvent before players are disconnected (#1626)
* feat: delay player disconnect until ProxyShutdownEvent completes

* fix: added back empty line

* feat: added ProxyPreShutdownEvent

* feat: CR changes

* chore: fixed license, annotated with Beta annotation

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* chore: consolidated log message

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>

* Update proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* Update api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* feat: make ProxyPreShutdownEvent timeout configurable via system property

* fix: cs

* Document velocity.pre-shutdown-timeout system property

---------

Co-authored-by: Timon Seidel <timong.seidel@gmail.com>
Co-authored-by: powercas_gamer <cas@mizule.dev>
Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-22 22:27:25 +01:00
Ecconia
87f74eaeda [ci skip] Improve documentation for priority parameter in EventManager (#1619)
* Improve documentation for priority parameter in EventManager

PostOrder was deprecated in commit
 (4f227badc2) in favor of priorities.
PostOrder itself was very descriptive on which PostOrder is processed first. A number cannot be descriptive about that - it is never clear if higher or lower numbers are processed first.

The Subscribe event attribute does contain a description on how priorities are evaluated. The EventManager did not, which literally did confused developers manually registering events.

This commit fixes this by describing the priority argument in EventManager with the same description that Subscribe uses.

* Fixed checkstyle

---------

Co-authored-by: Adrian Gonzales <adriangonzalesval@gmail.com>
2025-09-22 14:34:06 -05:00
ᑕᖇEEᑭYᑕᖇEEᑭEᖇ
8406979e71 Fix Weird Behavior in the Annotation Processor (#1645)
* fix: weird behavior of the annotation processor

* optimize imports
2025-09-17 12:17:48 -05:00
VelVeV
6e80f57739 Bump to Netty 4.2.5 (#1643) 2025-09-07 13:05:41 -05:00
Emil
1532fb360b fix: forward custom click packet in config state (#1641) 2025-09-03 22:43:02 +01:00
Emil
180af8c844 fix(resourcepack): apply server-side translations to resource pack prompt (#1611) 2025-09-03 11:09:12 -05:00
Timon Seidel
311e2bc18d fix(temp): pass though custom click action in config state (#1640) 2025-09-03 03:44:52 +01:00
Timon Seidel
bfd15e1a81 fix: kick logging ignoring config (#1636) 2025-08-31 10:14:32 -05:00
Shane Freeder
d2d333a958 Bumpy netty to 4.2.4 2025-08-14 21:14:51 +01:00
Emil
60a22ff330 chore: bump adventure to 4.24.0 (#1628) 2025-08-14 17:44:06 +01:00
Emil
946e5c47d4 fix: send callback command to >= 1.21.6 clients (#1627) 2025-08-14 17:43:20 +01:00
Gero
5d450ab3c7 Support all component-like and literal tooltips and errors (#1600) 2025-08-13 15:12:15 -05:00
Timon Seidel
a509a878e9 [ci skip] chore: migrate legacy url (#1606) 2025-08-13 14:09:52 -05:00
Andrew Steinborn
49e2988e37 Utilize ByteBuf.readString() 2025-08-08 21:47:49 -04:00
Andrew Steinborn
db8d16fd6e Bump to Netty 4.2.3
Closes #1615
2025-08-08 21:44:05 -04:00
Emil
d47848cb93 feat: map show_dialog & clear_dialog in CONFIG state (#1621) 2025-08-02 17:43:06 +01:00
873fca763d Merge remote-tracking branch 'upstream/dev/3.0.0'
All checks were successful
SteamWarCI Build successful
2025-07-28 18:34:52 +02:00
Pantera (Mad_Daniel)
e99407132f Add version information for 1.21.8 (#1612) 2025-07-18 03:26:24 +01:00
67d63faeca Reapply "Disable io_uring transport by default"
All checks were successful
SteamWarCI Build successful
This reverts commit 11834de220.
2025-07-10 10:42:11 +02:00
871b053561 Merge remote-tracking branch 'upstream/dev/3.0.0' 2025-07-10 10:41:50 +02:00
Shane Freeder
81deb1fff8 Update maven publishing repo name 2025-06-30 15:22:39 +01:00
Jones
59560ebad1 1.21.7 Support (#1598)
* Support 1.21.7 RC 1

* Use snapshot protocol for RC 1

* Support 1.21.7 RC 2

* Set release protocol for 1.21.7

* Update api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java

---------

Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
2025-06-30 16:09:39 +02:00
Limbo
67a6600c05 New Crowdin updates (#1283)
* New translations messages.properties (Bulgarian)

* New translations messages.properties (Spanish)
2025-06-29 23:31:24 -07:00
Christoph Loy
f3e30558e4 Gradle deprecation fixes & upgrades (#1594)
* Fix Gradle deprecations

By using test suites, we explicitely configure the relevant dependencies
on the test sourceset. This is not done by merely configuring the test task.

* Switch to maintained version of Shadow

* Update to Gradle 8.14.2
2025-06-28 16:28:29 -07:00
Riley Park
e46ab6ad7d build: publish using fill (#1599) 2025-06-28 16:12:00 -07:00
Gero
b6fd48f282 Update to adventure 4.22.0 (#1595) 2025-06-27 16:56:05 +01:00
c2edc26d8e Merge pull request 'Update Velocity' (#2) from update into master
All checks were successful
SteamWarCI Build successful
Reviewed-on: #2
2025-06-26 23:11:02 +02:00
76417b13d4 Merge branch 'updatev2' into update
All checks were successful
SteamWarCI Build successful
2025-06-26 22:53:32 +02:00
47f36e3ff9 Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
All checks were successful
SteamWarCI Build successful
2025-06-26 22:52:31 +02:00
SpigotRCE
10e75b6d55 feat: property support for max clientside channels (#1557) 2025-06-21 10:05:04 +02:00
Shane Freeder
fe69214e77 Downgrade netty (Fixes #1591) 2025-06-17 18:46:22 +01:00
Gero
020c7fe6f5 1.21.6 (#1580) 2025-06-17 14:52:50 +01:00
Shane Freeder
44bc15db40 Add system property to skip packet compression threshold validation 2025-06-14 20:29:12 +01:00
Tofik ♡
549b8d624e Update some dependencies (#1576) 2025-06-14 19:53:22 +01:00
Shane Freeder
669fda298c Only apply max known pack restrictions Serverbound 2025-06-11 10:12:48 +01:00
75bb48d00e Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
All checks were successful
SteamWarCI Build successful
2025-06-03 23:12:58 +02:00
AR
21ecd344ba Fix HoverEvent.showEntity() in protocol versions prior to 1.21.5 (#1578) 2025-05-25 21:24:21 +01:00
Shane Freeder
8c8162dbf6 Discard known packs if we don't have a target 2025-05-24 18:28:54 +01:00
Alex
5e20ec19ff Stabilize and expose suggestions API (#1406)
* Expose suggestions API

* Improve javadoc of suggestions api
2025-05-23 14:23:38 +01:00
Shane Freeder
5eb83760cd Attempt to improve partial read situations during early connections 2025-05-22 14:26:48 +01:00
Shane Freeder
8fea43d6ba Add some means to quickly overlook deframing issues 2025-05-22 13:13:06 +01:00
Kezz
678c7aa3a4 Modern-ify Adventure uses and fix bug in TranslatableMapper (#1575)
* fix: Don't ignore the player's locale in message translation

* feature: Use PointersSupplier to save constructing a Pointers instance for every player

* fix: Don't use a custom implementation of Identity for players

We don't need to carry about this object for every player.

* chore: Stop using deprecated TranslationRegistry

* fix: Simplify TranslatableMapper and fix bugs

- The fallback string is not intended to be translated, so don't do that.
- Check if the string can be translated in the default locale before using the closest mapper as devs may have their own strings.
- Remove the hardcoded check for TranslationRegistry instance as devs (and us now) can use non-TranslationRegistry translator instances.
2025-05-21 18:06:26 +01:00
Shane Freeder
e13c8c340f Increase limit to account for strings being strings 2025-05-09 22:17:04 +01:00
Shane Freeder
dc659538d3 Set netty allocator earlier and more globally 2025-05-09 20:19:29 +01:00
Shane Freeder
eb099d1220 Merge branches 'dev/3.0.0' and 'dev/3.0.0' of github.com:PaperMC/Velocity into dev/3.0.0 2025-05-09 15:55:40 +01:00
chris
1ad1f3b215 Use pooled netty allocator instead of default adaptive allocator (#1570) 2025-05-08 11:30:55 +01:00
Timon Seidel
063065b21a fix: adventure 4.21.0 adaptation (#1569) 2025-05-08 09:55:16 +01:00
Shane Freeder
00016ba4e1 Validate handshake packet length early 2025-05-08 00:15:50 +01:00
11834de220 Revert "Disable io_uring transport by default"
All checks were successful
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-05-02 20:49:11 +02:00
Timon Seidel
b411a0fa09 chore: bump adventure to 4.21.0 (#1564)
feat: support for 1.21.5+ hover and click events
2025-04-30 12:44:12 -07:00
Aaron
1561ba2e38 Bump adventure to 4.20.0 (#1544) 2025-04-28 16:13:48 -05:00
91a61643bd Revert "Disable io_uring transport by default"
All checks were successful
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-04-27 20:24:41 +02:00
b6e05cb0b9 Refactor TCP Fast Open checks and update message identifiers.
All checks were successful
SteamWarCI Build successful
Removed transport type conditions for TCP Fast Open to streamline configuration usage. Added imports for new message identifiers in `ClientPlaySessionHandler`. Cleaned up Netty library definitions in `libs.versions.toml`.
2025-04-27 20:09:05 +02:00
1507b91463 Merge remote-tracking branch 'upstream/dev/3.0.0' into update
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java
2025-04-27 19:53:15 +02:00
Shane Freeder
bd2bb6325e Validate state transition 2025-04-17 18:59:49 +01:00
booky
3f0a85d794 Fix MinecraftChannelIdentifier parsing to align with vanilla (#1552) 2025-04-14 13:52:02 -04:00
Shane Freeder
74d05211d6 Also validate length before caring to invest time into processing 2025-04-12 16:52:31 +01:00
Shane Freeder
7ad06614fe Appease checkstyle gods 2025-04-12 16:22:06 +01:00
Shane Freeder
163a85a468 Merge branch 'cleanup/plugin-message-channel-handling' into dev/3.0.0 2025-04-12 16:20:21 +01:00
Shane Freeder
a51711e4bb Use an ImmutableList Builder 2025-04-12 16:20:07 +01:00
Andrew Steinborn
ae312339a3 Disable io_uring transport by default 2025-04-11 00:35:49 -04:00
Shane Freeder
a429bb53ce Merge remote-tracking branch 'origin/dev/3.0.0' into cleanup/plugin-message-channel-handling 2025-04-09 10:06:40 +01:00
Andrew Steinborn
a549880df1 Bump to Netty 4.2.0 (#1380) 2025-04-09 01:21:08 -04:00
Shane Freeder
9c1be72db0 Fix tests 2025-04-06 21:25:08 +01:00
Shane Freeder
747f70d80a Appease checkstyle 2025-04-06 21:25:03 +01:00
Shane Freeder
b482443e79 Fix spot 2025-04-06 21:22:22 +01:00
Shane Freeder
676ec9cb21 preliminary cleanup of plugin message channel handling 2025-04-06 21:09:14 +01:00
微莹·纤绫
aae97dce3d Track plugin message channels that registered by clients (#1276) 2025-04-06 19:40:17 +01:00
Shane Freeder
c72a3eefde Check if kicking on command rate limit is enabled 2025-04-03 22:51:26 +01:00
SpigotRCE
86b88cf4b7 fix: typo 2025-04-03 18:59:16 +05:30
Bridge
7ffa43f0e2 feat: implement command rate limiter (#1524) 2025-04-03 13:26:00 +01:00
BBaoVanC
b3e218bd7d Show proxy-wide online players in server ping (#811)
* Show proxy-wide online players in server ping

* Reflow arguments in VelocityConfiguration constructor
2025-04-01 10:48:00 -05:00
Lixfel
b06af3718c Merge remote-tracking branch 'github/dev/3.0.0'
All checks were successful
SteamWarCI Build successful
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java
2025-04-01 07:06:14 +02:00
Timon Seidel
9324a52ce0 fix: server link's custom labels not being translated (#1537) 2025-03-31 12:58:43 -05:00
Timon Seidel
cc93f5eea4 feat: improve tablist (#1532)
* fix: setDisplayName in TabListEntry duplicating players on 1.7.10 (#1530)

* feat: expose toggling hat layer in TabListEntry (#1531)
2025-03-30 12:52:54 -05:00
Timon Seidel
c3d10bd410 feat: add function to directly pass collections in ServerPing.Builder (#1538) 2025-03-30 12:49:36 -05:00
Aaron
d2cd79185b Minecraft 1.21.5 (#1489)
Missing adventure component changes, so entity and item hovers from the API may not work for 1.21.5 clients

Co-authored-by: Gero <gecam59@gmail.com>
Co-authored-by: Shane Freeder <theboyetronic@gmail.com>
2025-03-27 15:56:15 +01:00
Gegy
4df640268f Fix: discard chat queue and chat state when switching servers (#1534)
This has two effects:
- Will no longer send queued chat packets from previous server after switch (race condition)
- The offset in 'last seen' updates will be corrected, as the internal ChatState will be reset (only applied if the player had not sent a message in a while, and >20 messages had been received)
2025-03-21 12:28:13 +00:00
Jones
d9f1016bd5 Validate uncompressed packet size (#1527)
* Validate uncompressed packet size

* Fix debug using incorrect value
2025-03-14 15:26:59 +00:00
Warrior
c9aa1cca09 Enable use tab in javadocs (#1525) 2025-03-10 15:11:01 +00:00
Riley Park
f980037bfd chore(proxy): require explicitly setting velocity.command.info permission to true 2025-03-06 19:14:18 -08:00
Andrew Steinborn
8f3dea5427 Bump Netty version 2025-02-28 22:21:32 -05:00
Gero
b8fe3577c9 Use correct component serializer in ComponentHolder#getBinaryTag 2025-02-28 15:01:30 +01:00
Andrew Steinborn
d4ea40a4a2 Add support for SO_REUSEPORT 2025-02-27 23:42:39 -05:00
Adrian
0afe061224 Updated Adventure to 4.19.0 (#1520)
Also updated ASM and Ansi to support Java 23 and 24
2025-02-27 01:25:01 -05:00
ST3V1K
58816c804a fix: problem with PluginMessageEvents for configuration phase (#1517) 2025-02-22 16:08:37 +00:00
Tommy
e69213f987 respect log-player-connections flag in config for connection messages (#1503) 2025-02-17 12:07:06 +00:00
Shane Freeder
f986eb51ec Do not print an exception if a client closed before switching to config state 2025-02-13 12:12:33 +00:00
Shane Freeder
9d1332d3a3 Add PluginMessageEvents for configuration phase 2025-02-13 11:53:30 +00:00
Andre_601
83c1749eed Add javadoc for ServerPing.Builder (#1500)
* Add javadoc for ServerPing.Builder

* Address codestyle reports
2025-02-12 14:35:38 -05:00
Kezz
a26d5581c4 docs: Remove beta annotations on events (#1505)
These events aren't in beta, some of them have been stable for years now.
2025-02-12 14:35:15 -05:00
Nassim Jahnke
1652d44f5f Fix checkstyle 💩 2025-01-31 19:35:43 +01:00
Nassim Jahnke
6815808d32 Improve fml mod list parsing 2025-01-31 19:11:34 +01:00
Outfluencer
91bdcebb1a Use real vanilla limits for LegacyChat (#1502)
Client -> server
< 1.11 has 100
>= 1.11 has 256

Server -> client has always 262144
2025-01-31 13:11:39 +00:00
scratchyone
0e84b57e53 Add Virtualhost support for server list pings (#1265)
* Add virtualhost support for server list pings

* Add virtualhost support to ping public API

* Applied suggestions

* fixed checkstyle

* Added nullable annotation

---------

Co-authored-by: Adrian <adriangonzalesval@gmail.com>
2025-01-31 07:04:52 -05:00
Isaac - The456
876b9c3601 Fix ShutdownCommand message styling (#1427)
* Fix ShutdownCommand message

* Fix checkstyle violation.
2025-01-26 23:03:37 -05:00
kyngs
6995f415d3 Expose shutdownInProgress to the API. (#1485)
Co-authored-by: kyngs <kyngs@users.noreply.github.com>
2025-01-26 07:49:35 -05:00
Shane Freeder
371e686076 properly apply vanilla cap to chat packets 2025-01-23 19:30:55 +00:00
Lixfel
a20a896582 Skip javadoc generation
All checks were successful
SteamWarCI Build successful
2025-01-22 09:37:30 +01:00
Lixfel
e1a3421212 Adapt to new server
Some checks failed
SteamWarCI Build failed
2025-01-22 09:33:59 +01:00
Henri Schubin
7392cd6574 Fix nonsensical deprecation for specifying listener priority (#1491)
* Fix nonsensical deprecation for specifying listener priority

* Fix checkstyle error
2025-01-21 12:37:20 -05:00
Shane Freeder
71feb11b2e Fix fallback compression handler 2025-01-17 15:11:03 +00:00
Gero
c0fdf20224 Add InboundConnection#getHandshakeIntent (#1493)
* Add InboundConnection#getHandshakeIntent
2025-01-14 20:44:20 -05:00
TangJin
00b68859ff Add "GetPlayerServer". (#1484) 2025-01-02 09:53:53 +00:00
Aaron
1db8c8c6ab Bump adventure to 4.18.0 (#1481) 2024-12-23 02:59:13 +00:00
Andrew Steinborn
af97ffffa5 A few small code cleanups for cryptography
* Remove some unused cryptographic code
* Add some notes about how Minecraft's cryptography choices have not quite survived the test of time
2024-12-21 03:45:17 -05:00
Kichura
39191957ea Migrate to setup-gradle, Gradle 8.11.1. (#1480) 2024-12-21 03:02:30 -05:00
David
d77e508e9c [ci skip] Fix typo in TabListEntry latency docs (#1479) 2024-12-19 16:04:56 +00:00
Ivan
4aa9ee7735 fix: NoClassDefFoundError on FastUtil's toIntArray (#1475) 2024-12-14 20:48:08 +00:00
176 changed files with 2982 additions and 711 deletions

View File

@@ -10,13 +10,14 @@ jobs:
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Validate Gradle Wrapper with:
uses: gradle/actions/wrapper-validation@v3 persist-credentials: false
- name: Set up JDK 17 - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 21
distribution: 'temurin' distribution: 'zulu'
cache: 'gradle'
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build

View File

@@ -61,6 +61,7 @@ tasks {
o.encoding = "UTF-8" o.encoding = "UTF-8"
o.source = "17" o.source = "17"
o.use()
o.links( o.links(
"https://www.slf4j.org/apidocs/", "https://www.slf4j.org/apidocs/",
"https://guava.dev/releases/${libs.guava.get().version}/api/docs/", "https://guava.dev/releases/${libs.guava.get().version}/api/docs/",
@@ -69,7 +70,7 @@ tasks {
"https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/", "https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/",
"https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/", "https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/",
"https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/", "https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/",
"https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/", "https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/",
) )
o.tags( o.tags(

View File

@@ -14,7 +14,6 @@ import com.velocitypowered.api.plugin.Plugin;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
@@ -68,8 +67,8 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
Name qualifiedName = ((TypeElement) element).getQualifiedName(); Name qualifiedName = ((TypeElement) element).getQualifiedName();
if (Objects.equals(pluginClassFound, qualifiedName.toString())) { if (pluginClassFound != null) {
if (!warnedAboutMultiplePlugins) { if (!pluginClassFound.equals(qualifiedName.toString()) && !warnedAboutMultiplePlugins) {
environment.getMessager() environment.getMessager()
.printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " .printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
+ "multiple plugins. We are using " + pluginClassFound + "multiple plugins. We are using " + pluginClassFound

View File

@@ -7,8 +7,10 @@
package com.velocitypowered.api.command; package com.velocitypowered.api.command;
import com.mojang.brigadier.suggestion.Suggestions;
import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.CommandExecuteEvent;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -116,6 +118,27 @@ public interface CommandManager {
*/ */
CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine); CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine);
/**
* Asynchronously collects suggestions to fill in the given command {@code cmdLine}.
* Returns only the raw completion suggestions without tooltips.
*
* @param source the source to execute the command for
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
*/
CompletableFuture<List<String>> offerSuggestions(CommandSource source, String cmdLine);
/**
* Asynchronously collects suggestions to fill in the given command {@code cmdLine}.
* Returns the brigadier {@link Suggestions} with tooltips for each result.
*
* @param source the source to execute the command for
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly
* empty
*/
CompletableFuture<Suggestions> offerBrigadierSuggestions(CommandSource source, String cmdLine);
/** /**
* Returns an immutable collection of the case-insensitive aliases registered * Returns an immutable collection of the case-insensitive aliases registered
* on this manager. * on this manager.

View File

@@ -27,7 +27,7 @@ public interface CommandSource extends Audience, PermissionSubject {
* for more information on the format. * for more information on the format.
**/ **/
default void sendRichMessage(final @NotNull String message) { default void sendRichMessage(final @NotNull String message) {
this.sendMessage(MiniMessage.miniMessage().deserialize(message)); this.sendMessage(MiniMessage.miniMessage().deserialize(message, this));
} }
/** /**
@@ -43,7 +43,7 @@ public interface CommandSource extends Audience, PermissionSubject {
final @NotNull String message, final @NotNull String message,
final @NotNull TagResolver @NotNull... resolvers final @NotNull TagResolver @NotNull... resolvers
) { ) {
this.sendMessage(MiniMessage.miniMessage().deserialize(message, resolvers)); this.sendMessage(MiniMessage.miniMessage().deserialize(message, this, resolvers));
} }
/** /**

View File

@@ -60,7 +60,8 @@ public interface EventManager {
* *
* @param plugin the plugin to associate with the handler * @param plugin the plugin to associate with the handler
* @param eventClass the class for the event handler to register * @param eventClass the class for the event handler to register
* @param postOrder the relative order in which events should be posted to the handler * @param postOrder the relative order in which events should be posted to the handler. The higher
* the priority, the earlier the event handler will be called
* @param handler the handler to register * @param handler the handler to register
* @param <E> the event type to handle * @param <E> the event type to handle
*/ */

View File

@@ -12,6 +12,14 @@ package com.velocitypowered.api.event;
*/ */
public enum PostOrder { public enum PostOrder {
FIRST, EARLY, NORMAL, LATE, LAST, CUSTOM FIRST, EARLY, NORMAL, LATE, LAST,
/**
* Previously used to specify that {@link Subscribe#priority()} should be used.
*
* @deprecated No longer required, you only need to specify {@link Subscribe#priority()}.
*/
@Deprecated
CUSTOM
} }

View File

@@ -32,12 +32,9 @@ public @interface Subscribe {
* The priority of this event handler. Priorities are used to determine the order in which event * The priority of this event handler. Priorities are used to determine the order in which event
* handlers are called. The higher the priority, the earlier the event handler will be called. * handlers are called. The higher the priority, the earlier the event handler will be called.
* *
* <p>Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM}
* in order to use this field.</p>
*
* @return the priority * @return the priority
*/ */
short priority() default Short.MIN_VALUE; short priority() default 0;
/** /**
* Whether the handler must be called asynchronously. By default, all event handlers are called * Whether the handler must be called asynchronously. By default, all event handlers are called

View File

@@ -9,7 +9,6 @@ package com.velocitypowered.api.event.command;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.Beta;
import com.mojang.brigadier.tree.RootCommandNode; import com.mojang.brigadier.tree.RootCommandNode;
import com.velocitypowered.api.event.annotation.AwaitingEvent; import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
@@ -21,7 +20,6 @@ import com.velocitypowered.api.proxy.Player;
* client. * client.
*/ */
@AwaitingEvent @AwaitingEvent
@Beta
public class PlayerAvailableCommandsEvent { public class PlayerAvailableCommandsEvent {
private final Player player; private final Player player;

View File

@@ -7,7 +7,6 @@
package com.velocitypowered.api.event.player; package com.velocitypowered.api.event.player;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
@@ -18,7 +17,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* available in {@link Player#getCurrentServer()}. Velocity will not wait on this event to finish * available in {@link Player#getCurrentServer()}. Velocity will not wait on this event to finish
* firing. * firing.
*/ */
@Beta
public class ServerPostConnectEvent { public class ServerPostConnectEvent {
private final Player player; private final Player player;
private final RegisteredServer previousServer; private final RegisteredServer previousServer;

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.proxy;
import com.google.common.annotations.Beta;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
/**
* This event is fired by the proxy after it has stopped accepting new connections,
* but before players are disconnected.
* This is the last point at which you can interact with currently connected players,
* for example to transfer them to another proxy or perform other cleanup tasks.
*
* @implNote Velocity will wait for all event listeners to complete before disconnecting players,
* but note that the event will time out after the configured value of the
* <code>velocity.pre-shutdown-timeout</code> system property, default 10 seconds,
* in seconds to prevent shutdown from hanging indefinitely
* @since 3.4.0
*/
@Beta
@AwaitingEvent
public final class ProxyPreShutdownEvent {
@Override
public String toString() {
return "ProxyPreShutdownEvent";
}
}

View File

@@ -7,7 +7,6 @@
package com.velocitypowered.api.event.proxy.server; package com.velocitypowered.api.event.proxy.server;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
@@ -23,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
* @param registeredServer A {@link RegisteredServer} that has been registered. * @param registeredServer A {@link RegisteredServer} that has been registered.
* @since 3.3.0 * @since 3.3.0
*/ */
@Beta
public record ServerRegisteredEvent(@NotNull RegisteredServer registeredServer) { public record ServerRegisteredEvent(@NotNull RegisteredServer registeredServer) {
public ServerRegisteredEvent { public ServerRegisteredEvent {
Preconditions.checkNotNull(registeredServer, "registeredServer"); Preconditions.checkNotNull(registeredServer, "registeredServer");

View File

@@ -7,7 +7,6 @@
package com.velocitypowered.api.event.proxy.server; package com.velocitypowered.api.event.proxy.server;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
@@ -23,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
* @param unregisteredServer A {@link RegisteredServer} that has been unregistered. * @param unregisteredServer A {@link RegisteredServer} that has been unregistered.
* @since 3.3.0 * @since 3.3.0
*/ */
@Beta
public record ServerUnregisteredEvent(@NotNull RegisteredServer unregisteredServer) { public record ServerUnregisteredEvent(@NotNull RegisteredServer unregisteredServer) {
public ServerUnregisteredEvent { public ServerUnregisteredEvent {
Preconditions.checkNotNull(unregisteredServer, "unregisteredServer"); Preconditions.checkNotNull(unregisteredServer, "unregisteredServer");

View File

@@ -89,7 +89,11 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"), MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
MINECRAFT_1_21(767, "1.21", "1.21.1"), MINECRAFT_1_21(767, "1.21", "1.21.1"),
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"), MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"),
MINECRAFT_1_21_4(769, "1.21.4"); MINECRAFT_1_21_4(769, "1.21.4"),
MINECRAFT_1_21_5(770, "1.21.5"),
MINECRAFT_1_21_6(771, "1.21.6"),
MINECRAFT_1_21_7(772, "1.21.7", "1.21.8"),
MINECRAFT_1_21_9(773, "1.21.9", "1.21.10");
private static final int SNAPSHOT_BIT = 30; private static final int SNAPSHOT_BIT = 30;

View File

@@ -7,6 +7,7 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.network.HandshakeIntent;
import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -60,4 +61,11 @@ public interface InboundConnection {
* @return the protocol state of the connection * @return the protocol state of the connection
*/ */
ProtocolState getProtocolState(); ProtocolState getProtocolState();
/**
* Returns the initial intent for the connection.
*
* @return the intent of the connection
*/
HandshakeIntent getHandshakeIntent();
} }

View File

@@ -29,6 +29,7 @@ import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import net.kyori.adventure.dialog.DialogLike;
import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.inventory.Book; import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
@@ -48,7 +49,7 @@ public interface Player extends
/* Fundamental Velocity interfaces */ /* Fundamental Velocity interfaces */
CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink, CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink,
/* Adventure-specific interfaces */ /* Adventure-specific interfaces */
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable { Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable, Sound.Emitter {
/** /**
* Returns the player's current username. * Returns the player's current username.
@@ -383,8 +384,12 @@ public interface Player extends
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <b>This method is not currently implemented in Velocity *
* @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b> * and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/ */
@Override @Override
default void playSound(@NotNull Sound sound) { default void playSound(@NotNull Sound sound) {
@@ -393,8 +398,11 @@ public interface Player extends
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <b>This method is not currently implemented in Velocity * @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b> * and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/ */
@Override @Override
default void playSound(@NotNull Sound sound, double x, double y, double z) { default void playSound(@NotNull Sound sound, double x, double y, double z) {
@@ -403,18 +411,28 @@ public interface Player extends
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <b>This method is not currently implemented in Velocity * <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-146721">MC-146721</a>, stereo sounds are always played globally in 1.14+.
* and will not perform any actions.</b> *
* <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-138832">MC-138832</a>, the volume and pitch are ignored when using this method in 1.14 to 1.16.5.
*
* @param sound the sound to play
* @param emitter the emitter of the sound; may be another player of this player's server
* @since 3.4.0
* @sinceMinecraft 1.19.3
* @apiNote This method is currently only implemented for players on 1.19.3+
* and requires a present {@link #getCurrentServer} for the emitting player as well as this player.
*/ */
@Override @Override
default void playSound(@NotNull Sound sound, Sound.Emitter emitter) { default void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) {
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <b>This method is not currently implemented in Velocity * @param stop the sound and/or a sound source, to stop
* and will not perform any actions.</b> * @since 3.4.0
* @sinceMinecraft 1.19.3
* @apiNote This method is currently only implemented for players on 1.19.3+.
*/ */
@Override @Override
default void stopSound(@NotNull SoundStop stop) { default void stopSound(@NotNull SoundStop stop) {
@@ -425,11 +443,40 @@ public interface Player extends
* *
* <b>This method is not currently implemented in Velocity * <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b> * and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/ */
@Override @Override
default void openBook(@NotNull Book book) { default void openBook(@NotNull Book book) {
} }
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void showDialog(@NotNull DialogLike dialog) {
}
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void closeDialog() {
}
/** /**
* Transfers a Player to a host. * Transfers a Player to a host.
* *

View File

@@ -41,6 +41,13 @@ public interface ProxyServer extends Audience {
*/ */
void shutdown(); void shutdown();
/**
* Returns whether the proxy is currently shutting down.
*
* @return {@code true} if the proxy is shutting down, {@code false} otherwise
*/
boolean isShuttingDown();
/** /**
* Closes all listening endpoints for this server. * Closes all listening endpoints for this server.
* This includes the main minecraft listener and query channel. * This includes the main minecraft listener and query channel.

View File

@@ -148,4 +148,59 @@ public interface ProxyConfig {
* @return read timeout (in milliseconds) * @return read timeout (in milliseconds)
*/ */
int getReadTimeout(); int getReadTimeout();
/**
* Get the rate limit for how fast a player can execute commands.
*
* @return the command rate limit (in milliseconds)
*/
int getCommandRatelimit();
/**
* Get whether we should forward commands to the backend if the player is rate limited.
*
* @return whether to forward commands if rate limited
*/
boolean isForwardCommandsIfRateLimited();
/**
* Get the kick limit for commands that are rate limited.
* If this limit is 0 or less, the player will be not be kicked.
*
* @return the rate limited command rate limit
*/
int getKickAfterRateLimitedCommands();
/**
* Get whether the proxy should kick players who are command rate limited.
*
* @return whether to kick players who are rate limited
*/
default boolean isKickOnCommandRateLimit() {
return getKickAfterRateLimitedCommands() > 0;
}
/**
* Get the rate limit for how fast a player can tab complete.
*
* @return the tab complete rate limit (in milliseconds)
*/
int getTabCompleteRatelimit();
/**
* Get the kick limit for tab completes that are rate limited.
* If this limit is 0 or less, the player will be not be kicked.
*
* @return the rate limited command rate limit
*/
int getKickAfterRateLimitedTabCompletes();
/**
* Get whether the proxy should kick players who are tab complete rate limited.
*
* @return whether to kick players who are rate limited
*/
default boolean isKickOnTabCompleteRateLimit() {
return getKickAfterRateLimitedTabCompletes() > 0;
}
} }

View File

@@ -11,7 +11,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -21,8 +20,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/ */
public final class MinecraftChannelIdentifier implements ChannelIdentifier { public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9/\\-_]*");
private final String namespace; private final String namespace;
private final String name; private final String name;
@@ -39,7 +36,7 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
* @return a new channel identifier * @return a new channel identifier
*/ */
public static MinecraftChannelIdentifier forDefaultNamespace(String name) { public static MinecraftChannelIdentifier forDefaultNamespace(String name) {
return new MinecraftChannelIdentifier("minecraft", name); return new MinecraftChannelIdentifier(Key.MINECRAFT_NAMESPACE, name);
} }
/** /**
@@ -52,14 +49,10 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
public static MinecraftChannelIdentifier create(String namespace, String name) { public static MinecraftChannelIdentifier create(String namespace, String name) {
checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty"); checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty");
checkArgument(name != null, "namespace is null or empty"); checkArgument(name != null, "namespace is null or empty");
checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(), checkArgument(Key.parseableNamespace(namespace),
"namespace is not valid, must match: %s got %s", "namespace is not valid, must match: [a-z0-9_.-] got %s", namespace);
VALID_IDENTIFIER_REGEX.toString(), checkArgument(Key.parseableValue(name),
namespace); "name is not valid, must match: [a-z0-9/._-] got %s", name);
checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(),
"name is not valid, must match: %s got %s",
VALID_IDENTIFIER_REGEX.toString(),
name);
return new MinecraftChannelIdentifier(namespace, name); return new MinecraftChannelIdentifier(namespace, name);
} }
@@ -72,10 +65,9 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
public static MinecraftChannelIdentifier from(String identifier) { public static MinecraftChannelIdentifier from(String identifier) {
int colonPos = identifier.indexOf(':'); int colonPos = identifier.indexOf(':');
if (colonPos == -1) { if (colonPos == -1) {
throw new IllegalArgumentException("Identifier does not contain a colon."); return create(Key.MINECRAFT_NAMESPACE, identifier);
} } else if (colonPos == 0) {
if (colonPos + 1 == identifier.length()) { return create(Key.MINECRAFT_NAMESPACE, identifier.substring(1));
throw new IllegalArgumentException("Identifier is empty.");
} }
String namespace = identifier.substring(0, colonPos); String namespace = identifier.substring(0, colonPos);
String name = identifier.substring(colonPos + 1); String name = identifier.substring(colonPos + 1);

View File

@@ -40,6 +40,7 @@ public interface TabList {
* Adds a {@link TabListEntry} to the {@link Player}'s tab list. * Adds a {@link TabListEntry} to the {@link Player}'s tab list.
* *
* @param entry to add to the tab list * @param entry to add to the tab list
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
*/ */
void addEntry(TabListEntry entry); void addEntry(TabListEntry entry);
@@ -47,6 +48,7 @@ public interface TabList {
* Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s tab list. * Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s tab list.
* *
* @param entries to add to the tab list * @param entries to add to the tab list
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
*/ */
default void addEntries(Iterable<TabListEntry> entries) { default void addEntries(Iterable<TabListEntry> entries) {
for (TabListEntry entry : entries) { for (TabListEntry entry : entries) {
@@ -58,6 +60,7 @@ public interface TabList {
* Adds an array of {@link TabListEntry}'s to the {@link Player}'s tab list. * Adds an array of {@link TabListEntry}'s to the {@link Player}'s tab list.
* *
* @param entries to add to the tab list * @param entries to add to the tab list
* @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists
*/ */
default void addEntries(TabListEntry... entries) { default void addEntries(TabListEntry... entries) {
for (TabListEntry entry : entries) { for (TabListEntry entry : entries) {
@@ -187,6 +190,26 @@ public interface TabList {
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
*/ */
@Deprecated @Deprecated
default TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder) {
return buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, true);
}
/**
* Represents an entry in a {@link Player}'s tab list.
*
* @param profile the profile
* @param displayName the display name
* @param latency the latency
* @param gameMode the game mode
* @param chatSession the chat session
* @param listed the visible status of entry
* @param listOrder the order/priority of entry in the tab list
* @param showHat the visibility of this entry's hat layer
* @return the entry
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
*/
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder); int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat);
} }

View File

@@ -80,7 +80,7 @@ public interface TabListEntry extends KeyIdentifiable {
* <li>150-300 will display 4 bars</li> * <li>150-300 will display 4 bars</li>
* <li>300-600 will display 3 bars</li> * <li>300-600 will display 3 bars</li>
* <li>600-1000 will display 2 bars</li> * <li>600-1000 will display 2 bars</li>
* <li>A latency move than 1 second will display 1 bar</li> * <li>A latency greater than 1 second will display 1 bar</li>
* </ul> * </ul>
* *
* @return latency set for {@code this} entry * @return latency set for {@code this} entry
@@ -160,6 +160,27 @@ public interface TabListEntry extends KeyIdentifiable {
return this; return this;
} }
/**
* Returns whether this entry's hat layer is shown in the tab list.
*
* @return whether to show this entry's hat layer
* @sinceMinecraft 1.21.4
*/
default boolean isShowHat() {
return true;
}
/**
* Sets whether to show this entry's hat layer in the tab list.
*
* @param showHat whether to show this entry's hat layer
* @return {@code this}, for chaining
* @sinceMinecraft 1.21.4
*/
default TabListEntry setShowHat(boolean showHat) {
return this;
}
/** /**
* Returns a {@link Builder} to create a {@link TabListEntry}. * Returns a {@link Builder} to create a {@link TabListEntry}.
* *
@@ -183,6 +204,7 @@ public interface TabListEntry extends KeyIdentifiable {
private int gameMode = 0; private int gameMode = 0;
private boolean listed = true; private boolean listed = true;
private int listOrder = 0; private int listOrder = 0;
private boolean showHat;
private @Nullable ChatSession chatSession; private @Nullable ChatSession chatSession;
@@ -268,7 +290,7 @@ public interface TabListEntry extends KeyIdentifiable {
* Sets whether this entry should be visible. * Sets whether this entry should be visible.
* *
* @param listed to set * @param listed to set
* @return ${code this}, for chaining * @return {@code this}, for chaining
* @see TabListEntry#isListed() * @see TabListEntry#isListed()
*/ */
public Builder listed(boolean listed) { public Builder listed(boolean listed) {
@@ -280,7 +302,7 @@ public interface TabListEntry extends KeyIdentifiable {
* Sets the order/priority of this entry in the tab list. * Sets the order/priority of this entry in the tab list.
* *
* @param order to set * @param order to set
* @return ${code this}, for chaining * @return {@code this}, for chaining
* @sinceMinecraft 1.21.2 * @sinceMinecraft 1.21.2
* @see TabListEntry#getListOrder() * @see TabListEntry#getListOrder()
*/ */
@@ -289,6 +311,18 @@ public interface TabListEntry extends KeyIdentifiable {
return this; return this;
} }
/**
* Sets whether this entry's hat layer should be shown in the tab list.
*
* @param showHat to set
* @return {@code this}, for chaining
* @see TabListEntry#isShowHat()
*/
public Builder showHat(boolean showHat) {
this.showHat = showHat;
return this;
}
/** /**
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}. * Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
* *
@@ -301,7 +335,7 @@ public interface TabListEntry extends KeyIdentifiable {
if (profile == null) { if (profile == null) {
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry"); throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
} }
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder); return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, showHat);
} }
} }
} }

View File

@@ -15,6 +15,7 @@ import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.kyori.adventure.builder.AbstractBuilder; import net.kyori.adventure.builder.AbstractBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** /**
* Contains the parameters used to ping a {@link RegisteredServer}. * Contains the parameters used to ping a {@link RegisteredServer}.
@@ -30,10 +31,12 @@ public final class PingOptions {
public static final PingOptions DEFAULT = PingOptions.builder().build(); public static final PingOptions DEFAULT = PingOptions.builder().build();
private final ProtocolVersion protocolVersion; private final ProtocolVersion protocolVersion;
private final long timeout; private final long timeout;
private final String virtualHost;
private PingOptions(final Builder builder) { private PingOptions(final Builder builder) {
this.protocolVersion = builder.protocolVersion; this.protocolVersion = builder.protocolVersion;
this.timeout = builder.timeout; this.timeout = builder.timeout;
this.virtualHost = builder.virtualHost;
} }
/** /**
@@ -54,6 +57,16 @@ public final class PingOptions {
return this.timeout; return this.timeout;
} }
/**
* The virtual host to pass to the server for the ping.
*
* @return the virtual hostname to pass to the server for the ping
* @since 3.4.0
*/
public @Nullable String getVirtualHost() {
return this.virtualHost;
}
/** /**
* Create a new builder to assign values to a new PingOptions. * Create a new builder to assign values to a new PingOptions.
* *
@@ -68,10 +81,9 @@ public final class PingOptions {
if (o == null) { if (o == null) {
return false; return false;
} }
if (!(o instanceof PingOptions)) { if (!(o instanceof final PingOptions other)) {
return false; return false;
} }
final PingOptions other = (PingOptions) o;
return Objects.equals(this.protocolVersion, other.protocolVersion) return Objects.equals(this.protocolVersion, other.protocolVersion)
&& Objects.equals(this.timeout, other.timeout); && Objects.equals(this.timeout, other.timeout);
} }
@@ -97,6 +109,7 @@ public final class PingOptions {
public static final class Builder implements AbstractBuilder<PingOptions> { public static final class Builder implements AbstractBuilder<PingOptions> {
private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN; private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN;
private long timeout = 0; private long timeout = 0;
private String virtualHost = null;
private Builder() { private Builder() {
} }
@@ -146,6 +159,18 @@ public final class PingOptions {
return this; return this;
} }
/**
* Sets the virtual host to pass to the server for the ping.
*
* @param virtualHost the virtual hostname to pass to the server for the ping
* @return this builder
* @since 3.4.0
*/
public Builder virtualHost(final @Nullable String virtualHost) {
this.virtualHost = virtualHost;
return this;
}
/** /**
* Create a new {@link PingOptions} with the values of this Builder. * Create a new {@link PingOptions} with the values of this Builder.
* *

View File

@@ -14,11 +14,14 @@ import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.ModInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable; import net.kyori.adventure.text.Component;
import org.jspecify.annotations.Nullable;
/** /**
* Represents a 1.7 and above server list ping response. This class is immutable. * Represents a 1.7 and above server list ping response. This class is immutable.
@@ -27,7 +30,7 @@ public final class ServerPing {
private final Version version; private final Version version;
private final @Nullable Players players; private final @Nullable Players players;
private final net.kyori.adventure.text.Component description; private final @Nullable Component description;
private final @Nullable Favicon favicon; private final @Nullable Favicon favicon;
private final @Nullable ModInfo modinfo; private final @Nullable ModInfo modinfo;
private final boolean preventsChatReports = true; private final boolean preventsChatReports = true;
@@ -47,7 +50,7 @@ public final class ServerPing {
* @param modinfo the mods this server runs * @param modinfo the mods this server runs
*/ */
public ServerPing(Version version, @Nullable Players players, public ServerPing(Version version, @Nullable Players players,
net.kyori.adventure.text.Component description, @Nullable Favicon favicon, Component description, @Nullable Favicon favicon,
@Nullable ModInfo modinfo) { @Nullable ModInfo modinfo) {
this.version = Preconditions.checkNotNull(version, "version"); this.version = Preconditions.checkNotNull(version, "version");
this.players = players; this.players = players;
@@ -64,7 +67,8 @@ public final class ServerPing {
return Optional.ofNullable(players); return Optional.ofNullable(players);
} }
public net.kyori.adventure.text.Component getDescriptionComponent() { @Nullable
public Component getDescriptionComponent() {
return description; return description;
} }
@@ -151,7 +155,7 @@ public final class ServerPing {
private final List<SamplePlayer> samplePlayers = new ArrayList<>(); private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType = "FML"; private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>(); private final List<ModInfo.Mod> mods = new ArrayList<>();
private net.kyori.adventure.text.Component description; private Component description;
private @Nullable Favicon favicon; private @Nullable Favicon favicon;
private boolean nullOutPlayers; private boolean nullOutPlayers;
private boolean nullOutModinfo; private boolean nullOutModinfo;
@@ -160,31 +164,79 @@ public final class ServerPing {
} }
/**
* Uses the modified {@code version} info in the response.
*
* @param version version info to set
* @return this builder, for chaining
*/
public Builder version(Version version) { public Builder version(Version version) {
this.version = Preconditions.checkNotNull(version, "version"); this.version = Preconditions.checkNotNull(version, "version");
return this; return this;
} }
/**
* Uses the modified {@code onlinePlayers} number in the response.
*
* @param onlinePlayers number for online players to set
* @return this builder, for chaining
*/
public Builder onlinePlayers(int onlinePlayers) { public Builder onlinePlayers(int onlinePlayers) {
this.onlinePlayers = onlinePlayers; this.onlinePlayers = onlinePlayers;
return this; return this;
} }
/**
* Uses the modified {@code maximumPlayers} number in the response.
* <b>This will not modify the actual maximum players that can join the server.</b>
*
* @param maximumPlayers number for maximum players to set
* @return this builder, for chaining
*/
public Builder maximumPlayers(int maximumPlayers) { public Builder maximumPlayers(int maximumPlayers) {
this.maximumPlayers = maximumPlayers; this.maximumPlayers = maximumPlayers;
return this; return this;
} }
/**
* Uses the modified {@code players} array in the response.
*
* @param players array of SamplePlayers to add
* @return this builder, for chaining
*/
public Builder samplePlayers(SamplePlayer... players) { public Builder samplePlayers(SamplePlayer... players) {
this.samplePlayers.addAll(Arrays.asList(players)); this.samplePlayers.addAll(Arrays.asList(players));
return this; return this;
} }
/**
* Uses the modified {@code players} collection in the response.
*
* @param players collection of SamplePlayers to add
* @return this builder, for chaining
*/
public Builder samplePlayers(Collection<SamplePlayer> players) {
this.samplePlayers.addAll(players);
return this;
}
/**
* Uses the modified {@code modType} in the response.
*
* @param modType the mod type to set
* @return this builder, for chaining
*/
public Builder modType(String modType) { public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType"); this.modType = Preconditions.checkNotNull(modType, "modType");
return this; return this;
} }
/**
* Uses the modified {@code mods} array in the response.
*
* @param mods array of mods to use
* @return this builder, for chaining
*/
public Builder mods(ModInfo.Mod... mods) { public Builder mods(ModInfo.Mod... mods) {
this.mods.addAll(Arrays.asList(mods)); this.mods.addAll(Arrays.asList(mods));
return this; return this;
@@ -194,7 +246,7 @@ public final class ServerPing {
* Uses the modified {@code mods} list in the response. * Uses the modified {@code mods} list in the response.
* *
* @param mods the mods list to use * @param mods the mods list to use
* @return this build, for chaining * @return this builder, for chaining
*/ */
public Builder mods(ModInfo mods) { public Builder mods(ModInfo mods) {
Preconditions.checkNotNull(mods, "mods"); Preconditions.checkNotNull(mods, "mods");
@@ -204,36 +256,74 @@ public final class ServerPing {
return this; return this;
} }
/**
* Clears the current list of mods to use in the response.
*
* @return this builder, for chaining
*/
public Builder clearMods() { public Builder clearMods() {
this.mods.clear(); this.mods.clear();
return this; return this;
} }
/**
* Clears the current list of PlayerSamples to use in the response.
*
* @return this builder, for chaining
*/
public Builder clearSamplePlayers() { public Builder clearSamplePlayers() {
this.samplePlayers.clear(); this.samplePlayers.clear();
return this; return this;
} }
/**
* Defines the server as mod incompatible in the response.
*
* @return this builder, for chaining
*/
public Builder notModCompatible() { public Builder notModCompatible() {
this.nullOutModinfo = true; this.nullOutModinfo = true;
return this; return this;
} }
/**
* Enables nulling Players in the response.
* This will display the player count as {@code ???}.
*
* @return this builder, for chaining
*/
public Builder nullPlayers() { public Builder nullPlayers() {
this.nullOutPlayers = true; this.nullOutPlayers = true;
return this; return this;
} }
public Builder description(net.kyori.adventure.text.Component description) { /**
* Uses the {@code description} Component in the response.
*
* @param description Component to use as the description.
* @return this builder, for chaining
*/
public Builder description(Component description) {
this.description = Preconditions.checkNotNull(description, "description"); this.description = Preconditions.checkNotNull(description, "description");
return this; return this;
} }
/**
* Uses the {@code favicon} in the response.
*
* @param favicon Favicon instance to use.
* @return this builder, for chaining
*/
public Builder favicon(Favicon favicon) { public Builder favicon(Favicon favicon) {
this.favicon = Preconditions.checkNotNull(favicon, "favicon"); this.favicon = Preconditions.checkNotNull(favicon, "favicon");
return this; return this;
} }
/**
* Clears the current favicon used in the response.
*
* @return this builder, for chaining
*/
public Builder clearFavicon() { public Builder clearFavicon() {
this.favicon = null; this.favicon = null;
return this; return this;
@@ -273,7 +363,7 @@ public final class ServerPing {
return samplePlayers; return samplePlayers;
} }
public Optional<net.kyori.adventure.text.Component> getDescriptionComponent() { public Optional<Component> getDescriptionComponent() {
return Optional.ofNullable(description); return Optional.ofNullable(description);
} }
@@ -430,6 +520,10 @@ public final class ServerPing {
*/ */
public static final class SamplePlayer { public static final class SamplePlayer {
public static final SamplePlayer ANONYMOUS = new SamplePlayer(
"Anonymous Player",
new UUID(0L, 0L)
);
private final String name; private final String name;
private final UUID id; private final UUID id;

View File

@@ -76,9 +76,17 @@ public final class ModInfo {
private final String id; private final String id;
private final String version; private final String version;
/**
* Creates a new mod info.
*
* @param id the mod identifier
* @param version the mod version
*/
public Mod(String id, String version) { public Mod(String id, String version) {
this.id = Preconditions.checkNotNull(id, "id"); this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version"); this.version = Preconditions.checkNotNull(version, "version");
Preconditions.checkArgument(id.length() < 128, "mod id is too long");
Preconditions.checkArgument(version.length() < 128, "mod version is too long");
} }
public String getId() { public String getId() {

View File

@@ -47,17 +47,25 @@ class MinecraftChannelIdentifierTest {
create("velocity", "test/test2"); create("velocity", "test/test2");
} }
@Test
void fromIdentifierDefaultNamespace() {
assertEquals("minecraft", from("test").getNamespace());
assertEquals("minecraft", from(":test").getNamespace());
}
@Test
void fromIdentifierAllowsEmptyName() {
from("minecraft:");
from(":");
from("");
}
@Test @Test
void fromIdentifierThrowsOnBadValues() { void fromIdentifierThrowsOnBadValues() {
assertAll( assertAll(
() -> assertThrows(IllegalArgumentException.class, () -> from("")),
() -> assertThrows(IllegalArgumentException.class, () -> from(":")),
() -> assertThrows(IllegalArgumentException.class, () -> from(":a")),
() -> assertThrows(IllegalArgumentException.class, () -> from("a:")),
() -> assertThrows(IllegalArgumentException.class, () -> from("hello:$$$$$$")), () -> assertThrows(IllegalArgumentException.class, () -> from("hello:$$$$$$")),
() -> assertThrows(IllegalArgumentException.class, () -> from("he/llo:wor/ld")),
() -> assertThrows(IllegalArgumentException.class, () -> from("hello::")) () -> assertThrows(IllegalArgumentException.class, () -> from("hello::"))
); );
} }
} }

View File

@@ -2,8 +2,15 @@ import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
// This interface is needed as a workaround to get an instance of ExecOperations
interface Injected {
@get:Inject
val execOps: ExecOperations
}
val currentShortRevision = ByteArrayOutputStream().use { val currentShortRevision = ByteArrayOutputStream().use {
exec { val execOps = objects.newInstance<Injected>().execOps
execOps.exec {
executable = "git" executable = "git"
args = listOf("rev-parse", "HEAD") args = listOf("rev-parse", "HEAD")
standardOutput = it standardOutput = it

View File

@@ -8,10 +8,10 @@ extensions.configure<PublishingExtension> {
maven { maven {
credentials(PasswordCredentials::class.java) credentials(PasswordCredentials::class.java)
name = "paper" name = if (version.toString().endsWith("SNAPSHOT")) "paperSnapshots" else "paper" // "paper" is seemingly not defined
val base = "https://repo.papermc.io/repository/maven" val base = "https://artifactory.papermc.io/artifactory"
val releasesRepoUrl = "$base-releases/" val releasesRepoUrl = "$base/releases/"
val snapshotsRepoUrl = "$base-snapshots/" val snapshotsRepoUrl = "$base/snapshots/"
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
} }
} }

View File

@@ -20,11 +20,11 @@ subprojects {
testImplementation(rootProject.libs.junit) testImplementation(rootProject.libs.junit)
} }
tasks { testing.suites.named<JvmTestSuite>("test") {
test { useJUnitJupiter()
useJUnitPlatform() targets.all {
reports { testTask.configure {
junitXml.required.set(true) reports.junitXml.required = true
} }
} }
} }

View File

@@ -2,23 +2,24 @@
configurate3 = "3.7.3" configurate3 = "3.7.3"
configurate4 = "4.1.2" configurate4 = "4.1.2"
flare = "2.0.1" flare = "2.0.1"
log4j = "2.24.1" log4j = "2.24.3"
netty = "4.1.114.Final" netty = "4.2.7.Final"
[plugins] [plugins]
fill = "io.papermc.fill.gradle:1.0.3"
indra-publishing = "net.kyori.indra.publishing:2.0.6" indra-publishing = "net.kyori.indra.publishing:2.0.6"
shadow = "io.github.goooler.shadow:8.1.5" shadow = "com.gradleup.shadow:8.3.6"
spotless = "com.diffplug.spotless:6.25.0" spotless = "com.diffplug.spotless:6.25.0"
[libraries] [libraries]
adventure-bom = "net.kyori:adventure-bom:4.17.0" adventure-bom = "net.kyori:adventure-bom:4.25.0"
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.17.0" adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.25.0"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.4" adventure-facet = "net.kyori:adventure-platform-facet:4.3.4"
asm = "org.ow2.asm:asm:9.6" asm = "org.ow2.asm:asm:9.8"
auto-service = "com.google.auto.service:auto-service:1.0.1" auto-service = "com.google.auto.service:auto-service:1.0.1"
auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1" auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1"
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.0.2" bstats = "org.bstats:bstats-base:3.0.3"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8" caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
checker-qual = "org.checkerframework:checker-qual:3.42.0" checker-qual = "org.checkerframework:checker-qual:3.42.0"
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3" checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
@@ -33,11 +34,11 @@ disruptor = "com.lmax:disruptor:4.0.0"
fastutil = "it.unimi.dsi:fastutil:8.5.15" fastutil = "it.unimi.dsi:fastutil:8.5.15"
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
jline = "org.jline:jline-terminal-jansi:3.27.1" jline = "org.jline:jline-terminal-jansi:3.30.2"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:5.10.2" junit = "org.junit.jupiter:junit-jupiter:5.10.2"
jspecify = "org.jspecify:jspecify:0.3.0" jspecify = "org.jspecify:jspecify:0.3.0"
kyori-ansi = "net.kyori:ansi:1.1.0" kyori-ansi = "net.kyori:ansi:1.1.1"
guava = "com.google.guava:guava:25.1-jre" guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1" gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:6.0.0" guice = "com.google.inject:guice:6.0.0"
@@ -53,10 +54,10 @@ netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "
netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" } netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" } netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
netty-transport-native-iouring = { module = "io.netty.incubator:netty-incubator-transport-native-io_uring", version = "0.0.25.Final" }
netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" } netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" }
netty-transport-native-iouring = { module = "io.netty:netty-transport-native-io_uring", version.ref = "netty" }
nightconfig = "com.electronwill.night-config:toml:3.6.7" nightconfig = "com.electronwill.night-config:toml:3.6.7"
slf4j = "org.slf4j:slf4j-api:2.0.12" slf4j = "org.slf4j:slf4j-api:2.0.17"
snakeyaml = "org.yaml:snakeyaml:1.33" snakeyaml = "org.yaml:snakeyaml:1.33"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3" spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

9
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -200,7 +203,7 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,

4
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -34,7 +36,7 @@ set APP_HOME=%DIRNAME%
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 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. @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" set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome

View File

@@ -26,6 +26,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env,
return 0; return 0;
} }
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
//
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
// most serious.
//
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
CCCryptorRef cryptor = NULL; CCCryptorRef cryptor = NULL;
CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt, CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt,
kCCModeCFB8, kCCModeCFB8,

View File

@@ -32,6 +32,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env,
return 0; return 0;
} }
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
//
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
// most serious.
//
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes, int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes,
encrypt); encrypt);
if (result != 1) { if (result != 1) {

View File

@@ -57,7 +57,8 @@ public class JavaVelocityCompressor implements VelocityCompressor {
inflater.setInput(source.nioBuffer()); inflater.setInput(source.nioBuffer());
try { try {
while (!inflater.finished() && inflater.getBytesWritten() < uncompressedSize) { final int readable = source.readableBytes();
while (!inflater.finished() && inflater.getBytesRead() < readable) {
if (!destination.isWritable()) { if (!destination.isWritable()) {
destination.ensureWritable(ZLIB_BUFFER_SIZE); destination.ensureWritable(ZLIB_BUFFER_SIZE);
} }

View File

@@ -48,6 +48,15 @@ public class JavaVelocityCipher implements VelocityCipher {
private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException { private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding"); this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
// But, you're saying, *why* are we using the key as the IV? After all, reusing the key as
// the IV defeats the entire point - we might as well just initialize it to all zeroes.
//
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
// most serious.
//
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
new IvParameterSpec(key.getEncoded())); new IvParameterSpec(key.getEncoded()));
} }

View File

@@ -1,9 +1,11 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
import io.papermc.fill.model.BuildChannel
plugins { plugins {
application application
id("velocity-init-manifest") id("velocity-init-manifest")
alias(libs.plugins.shadow) alias(libs.plugins.shadow)
alias(libs.plugins.fill)
} }
application { application {
@@ -51,7 +53,11 @@ tasks {
exclude("it/unimi/dsi/fastutil/ints/*Int2Short*") exclude("it/unimi/dsi/fastutil/ints/*Int2Short*")
exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*") exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*")
exclude("it/unimi/dsi/fastutil/ints/IntAVL*") exclude("it/unimi/dsi/fastutil/ints/IntAVL*")
exclude("it/unimi/dsi/fastutil/ints/IntArray*") exclude("it/unimi/dsi/fastutil/ints/IntArrayF*")
exclude("it/unimi/dsi/fastutil/ints/IntArrayI*")
exclude("it/unimi/dsi/fastutil/ints/IntArrayL*")
exclude("it/unimi/dsi/fastutil/ints/IntArrayP*")
exclude("it/unimi/dsi/fastutil/ints/IntArraySet*")
exclude("it/unimi/dsi/fastutil/ints/*IntBi*") exclude("it/unimi/dsi/fastutil/ints/*IntBi*")
exclude("it/unimi/dsi/fastutil/ints/Int*Pair") exclude("it/unimi/dsi/fastutil/ints/Int*Pair")
exclude("it/unimi/dsi/fastutil/ints/IntLinked*") exclude("it/unimi/dsi/fastutil/ints/IntLinked*")
@@ -96,6 +102,7 @@ tasks {
runShadow { runShadow {
workingDir = file("run").also(File::mkdirs) workingDir = file("run").also(File::mkdirs)
standardInput = System.`in` standardInput = System.`in`
jvmArgs("-Dvelocity.packet-decode-logging=true")
} }
named<JavaExec>("run") { named<JavaExec>("run") {
workingDir = file("run").also(File::mkdirs) workingDir = file("run").also(File::mkdirs)
@@ -103,6 +110,24 @@ tasks {
} }
} }
val projectVersion = version as String
fill {
project("velocity")
build {
channel = BuildChannel.STABLE
versionFamily("3.0.0")
version(projectVersion)
downloads {
register("server:default") {
file = tasks.shadowJar.flatMap { it.archiveFile }
nameResolver.set { project, _, version, build -> "$project-$version-$build.jar" }
}
}
}
}
dependencies { dependencies {
implementation(project(":velocity-api")) implementation(project(":velocity-api"))
implementation(project(":velocity-native")) implementation(project(":velocity-native"))

View File

@@ -65,7 +65,8 @@ public class Metrics {
logger::info, logger::info,
config.isLogErrorsEnabled(), config.isLogErrorsEnabled(),
config.isLogSentDataEnabled(), config.isLogSentDataEnabled(),
config.isLogResponseStatusTextEnabled() config.isLogResponseStatusTextEnabled(),
false
); );
if (!config.didExistBefore()) { if (!config.didExistBefore()) {

View File

@@ -24,6 +24,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyPreShutdownEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent; import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
@@ -75,11 +76,13 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyPair; import java.security.KeyPair;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -103,7 +106,7 @@ import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry; import net.kyori.adventure.translation.TranslationStore;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bstats.MetricsBase; import org.bstats.MetricsBase;
@@ -117,7 +120,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/ */
public class VelocityServer implements ProxyServer, ForwardingAudience { public class VelocityServer implements ProxyServer, ForwardingAudience {
public static final String VELOCITY_URL = "https://velocitypowered.com"; public static final String VELOCITY_URL = "https://papermc.io/software/velocity";
private static final Logger logger = LogManager.getLogger(VelocityServer.class); private static final Logger logger = LogManager.getLogger(VelocityServer.class);
public static final Gson GENERAL_GSON = new GsonBuilder() public static final Gson GENERAL_GSON = new GsonBuilder()
@@ -148,6 +151,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
) )
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.create(); .create();
private static final int PRE_SHUTDOWN_TIMEOUT =
Integer.getInteger("velocity.pre-shutdown-timeout", 10);
private final ConnectionManager cm; private final ConnectionManager cm;
private final ProxyOptions options; private final ProxyOptions options;
@@ -162,7 +167,9 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>(); private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>(); private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private final VelocityConsole console; private final VelocityConsole console;
private @MonotonicNonNull Ratelimiter ipAttemptLimiter; private @MonotonicNonNull Ratelimiter<InetAddress> ipAttemptLimiter;
private @MonotonicNonNull Ratelimiter<UUID> commandRateLimiter;
private @MonotonicNonNull Ratelimiter<UUID> tabCompleteRateLimiter;
private final VelocityEventManager eventManager; private final VelocityEventManager eventManager;
private final VelocityScheduler scheduler; private final VelocityScheduler scheduler;
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
@@ -212,7 +219,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
ProxyVersion version = getVersion(); ProxyVersion version = getVersion();
PluginDescription description = new VelocityPluginDescription( PluginDescription description = new VelocityPluginDescription(
"velocity", version.getName(), version.getVersion(), "The Velocity proxy", "velocity", version.getName(), version.getVersion(), "The Velocity proxy",
VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null); version.getName().equals("Velocity") ? VELOCITY_URL : null,
ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
VelocityPluginContainer container = new VelocityPluginContainer(description); VelocityPluginContainer container = new VelocityPluginContainer(description);
container.setInstance(VelocityVirtualPlugin.INSTANCE); container.setInstance(VelocityVirtualPlugin.INSTANCE);
return container; return container;
@@ -236,6 +244,15 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
registerTranslations(); registerTranslations();
// Yes, you're reading that correctly. We're generating a 1024-bit RSA keypair. Sounds
// dangerous, right? We're well within the realm of factoring such a key...
//
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
// most serious.
//
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
cm.logChannelInformation(); cm.logChannelInformation();
@@ -286,6 +303,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
} }
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit()); ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
commandRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getCommandRatelimit());
tabCompleteRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getTabCompleteRatelimit());
loadPlugins(); loadPlugins();
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance // Go ahead and fire the proxy initialization event. We block since plugins should have a chance
@@ -323,8 +342,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
} }
private void registerTranslations() { private void registerTranslations() {
final TranslationRegistry translationRegistry = TranslationRegistry final TranslationStore.StringBased<MessageFormat> translationRegistry =
.create(Key.key("velocity", "translations")); TranslationStore.messageFormat(Key.key("velocity", "translations"));
translationRegistry.defaultLocale(Locale.US); translationRegistry.defaultLocale(Locale.US);
try { try {
ResourceUtils.visitResources(VelocityServer.class, path -> { ResourceUtils.visitResources(VelocityServer.class, path -> {
@@ -563,6 +582,20 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
// done first to refuse new connections // done first to refuse new connections
cm.shutdown(); cm.shutdown();
try {
eventManager.fire(new ProxyPreShutdownEvent())
.toCompletableFuture()
.get(PRE_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutException ignored) {
logger.warn("Your plugins took over {} seconds during pre shutdown.",
PRE_SHUTDOWN_TIMEOUT);
} catch (ExecutionException ee) {
logger.error("Exception in ProxyPreShutdownEvent handler; continuing shutdown.", ee);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
logger.warn("Interrupted while waiting for ProxyPreShutdownEvent; continuing shutdown.");
}
ImmutableList<ConnectedPlayer> players = ImmutableList.copyOf(connectionsByUuid.values()); ImmutableList<ConnectedPlayer> players = ImmutableList.copyOf(connectionsByUuid.values());
for (ConnectedPlayer player : players) { for (ConnectedPlayer player : players) {
player.disconnect(reason); player.disconnect(reason);
@@ -645,10 +678,18 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
return cm.createHttpClient(); return cm.createHttpClient();
} }
public Ratelimiter getIpAttemptLimiter() { public @MonotonicNonNull Ratelimiter<InetAddress> getIpAttemptLimiter() {
return ipAttemptLimiter; return ipAttemptLimiter;
} }
public @MonotonicNonNull Ratelimiter<UUID> getCommandRateLimiter() {
return commandRateLimiter;
}
public @MonotonicNonNull Ratelimiter<UUID> getTabCompleteRateLimiter() {
return tabCompleteRateLimiter;
}
/** /**
* Checks if the {@code connection} can be registered with the proxy. * Checks if the {@code connection} can be registered with the proxy.
* *
@@ -795,6 +836,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
return channelRegistrar; return channelRegistrar;
} }
@Override
public boolean isShuttingDown() {
return shutdownInProgress.get();
}
@Override @Override
public InetSocketAddress getBoundAddress() { public InetSocketAddress getBoundAddress() {
if (configuration == null) { if (configuration == null) {

View File

@@ -53,15 +53,23 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
viewer.getProtocolVersion(), viewer.getProtocolVersion(),
viewer.translateMessage(this.bar.name()) viewer.translateMessage(this.bar.name())
); );
viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name)); viewer.getBossBarManager().writeUpdate(this, BossBarPacket.createAddPacket(this.id, this.bar, name));
return true; return true;
} }
return false; return false;
} }
public void createDirect(final ConnectedPlayer viewer) {
final ComponentHolder name = new ComponentHolder(
viewer.getProtocolVersion(),
viewer.translateMessage(this.bar.name())
);
viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name));
}
public boolean viewerRemove(final ConnectedPlayer viewer) { public boolean viewerRemove(final ConnectedPlayer viewer) {
if (this.viewers.remove(viewer)) { if (this.viewers.remove(viewer)) {
viewer.getConnection().write(BossBarPacket.createRemovePacket(this.id, this.bar)); viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar));
return true; return true;
} }
return false; return false;
@@ -84,7 +92,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
this.bar, this.bar,
new ComponentHolder(viewer.getProtocolVersion(), translated) new ComponentHolder(viewer.getProtocolVersion(), translated)
); );
viewer.getConnection().write(packet); viewer.getBossBarManager().writeUpdate(this, packet);
} }
} }
@@ -96,7 +104,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) { ) {
final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar); final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) { for (final ConnectedPlayer viewer : this.viewers) {
viewer.getConnection().write(packet); viewer.getBossBarManager().writeUpdate(this, packet);
} }
} }
@@ -108,7 +116,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) { ) {
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) { for (final ConnectedPlayer viewer : this.viewers) {
viewer.getConnection().write(packet); viewer.getBossBarManager().writeUpdate(this, packet);
} }
} }
@@ -120,7 +128,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) { ) {
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) { for (final ConnectedPlayer viewer : this.viewers) {
viewer.getConnection().write(packet); viewer.getBossBarManager().writeUpdate(this, packet);
} }
} }
@@ -132,7 +140,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) { ) {
final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar); final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) { for (final ConnectedPlayer viewer : this.viewers) {
viewer.getConnection().write(packet); viewer.getBossBarManager().writeUpdate(this, packet);
} }
} }
} }

View File

@@ -35,7 +35,6 @@ import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandResult; import com.velocitypowered.api.command.CommandResult;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.PostCommandInvocationEvent; import com.velocitypowered.api.event.command.PostCommandInvocationEvent;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
@@ -59,6 +58,7 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -242,8 +242,8 @@ public class VelocityCommandManager implements CommandManager {
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand()); CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
if (isSyntaxError) { if (isSyntaxError) {
final Message message = e.getRawMessage(); final Message message = e.getRawMessage();
if (message instanceof VelocityBrigadierMessage velocityMessage) { if (message instanceof ComponentLike componentLike) {
source.sendMessage(velocityMessage.asComponent().applyFallbackStyle(NamedTextColor.RED)); source.sendMessage(componentLike.asComponent().applyFallbackStyle(NamedTextColor.RED));
} else { } else {
source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED)); source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
} }
@@ -300,27 +300,14 @@ public class VelocityCommandManager implements CommandManager {
); );
} }
/** @Override
* Returns suggestions to fill in the given command.
*
* @param source the source to execute the command for
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
*/
public CompletableFuture<List<String>> offerSuggestions(final CommandSource source, public CompletableFuture<List<String>> offerSuggestions(final CommandSource source,
final String cmdLine) { final String cmdLine) {
return offerBrigadierSuggestions(source, cmdLine) return offerBrigadierSuggestions(source, cmdLine)
.thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText)); .thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText));
} }
/** @Override
* Returns suggestions to fill in the given command.
*
* @param source the source to execute the command for
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly
* empty
*/
public CompletableFuture<Suggestions> offerBrigadierSuggestions( public CompletableFuture<Suggestions> offerBrigadierSuggestions(
final CommandSource source, final String cmdLine) { final CommandSource source, final String cmdLine) {
Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(source, "source");

View File

@@ -17,6 +17,7 @@
package com.velocitypowered.proxy.command.builtin; package com.velocitypowered.proxy.command.builtin;
import com.google.gson.JsonSyntaxException;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@@ -25,8 +26,9 @@ import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
/** /**
* Shuts down the proxy. * Shuts down the proxy.
@@ -53,11 +55,22 @@ public final class ShutdownCommand {
StringArgumentType.greedyString()) StringArgumentType.greedyString())
.executes(context -> { .executes(context -> {
String reason = context.getArgument("reason", String.class); String reason = context.getArgument("reason", String.class);
server.shutdown(true, MiniMessage.miniMessage().deserialize( Component reasonComponent = null;
MiniMessage.miniMessage().serialize(
LegacyComponentSerializer.legacy('&').deserialize(reason) if (reason.startsWith("{") || reason.startsWith("[") || reason.startsWith("\"")) {
) try {
)); reasonComponent = GsonComponentSerializer.gson()
.deserializeOrNull(reason);
} catch (JsonSyntaxException expected) {
}
}
if (reasonComponent == null) {
reasonComponent = MiniMessage.miniMessage().deserialize(reason);
}
server.shutdown(true, reasonComponent);
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
}) })
).build()); ).build());

View File

@@ -82,7 +82,7 @@ public final class VelocityCommand {
.executes(new Heap()) .executes(new Heap())
.build(); .build();
final LiteralCommandNode<CommandSource> info = BrigadierCommand.literalArgumentBuilder("info") final LiteralCommandNode<CommandSource> info = BrigadierCommand.literalArgumentBuilder("info")
.requires(source -> source.getPermissionValue("velocity.command.info") != Tristate.FALSE) .requires(source -> source.getPermissionValue("velocity.command.info") == Tristate.TRUE)
.executes(new Info(server)) .executes(new Info(server))
.build(); .build();
final LiteralCommandNode<CommandSource> plugins = BrigadierCommand final LiteralCommandNode<CommandSource> plugins = BrigadierCommand
@@ -176,8 +176,7 @@ public final class VelocityCommand {
.append(Component.text() .append(Component.text()
.content("PaperMC") .content("PaperMC")
.color(NamedTextColor.GREEN) .color(NamedTextColor.GREEN)
.clickEvent( .clickEvent(ClickEvent.openUrl(VelocityServer.VELOCITY_URL))
ClickEvent.openUrl("https://papermc.io/software/velocity"))
.build()) .build())
.append(Component.text(" - ")) .append(Component.text(" - "))
.append(Component.text() .append(Component.text()

View File

@@ -103,13 +103,17 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
.requiresWithContext((context, reader) -> requirement.test(context)) .requiresWithContext((context, reader) -> requirement.test(context))
.executes(callback) .executes(callback)
.suggests((context, builder) -> { .suggests((context, builder) -> {
// Offset the suggestion to the last space seperated word
int lastSpace = builder.getRemaining().lastIndexOf(' ') + 1;
final var offsetBuilder = builder.createOffset(builder.getStart() + lastSpace);
final I invocation = invocationFactory.create(context); final I invocation = invocationFactory.create(context);
return command.suggestAsync(invocation).thenApply(suggestions -> { return command.suggestAsync(invocation).thenApply(suggestions -> {
for (String value : suggestions) { for (String value : suggestions) {
Preconditions.checkNotNull(value, "suggestion"); Preconditions.checkNotNull(value, "suggestion");
builder.suggest(value); offsetBuilder.suggest(value);
} }
return builder.build(); return offsetBuilder.build();
}); });
}) })
.build(); .build();

View File

@@ -78,6 +78,8 @@ public class VelocityConfiguration implements ProxyConfig {
private boolean onlineModeKickExistingPlayers = false; private boolean onlineModeKickExistingPlayers = false;
@Expose @Expose
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
@Expose
private boolean samplePlayersInPing = false;
private final Servers servers; private final Servers servers;
private final ForcedHosts forcedHosts; private final ForcedHosts forcedHosts;
@Expose @Expose
@@ -105,8 +107,9 @@ public class VelocityConfiguration implements ProxyConfig {
boolean preventClientProxyConnections, boolean announceForge, boolean preventClientProxyConnections, boolean announceForge,
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts, boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) { ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
boolean forceKeyAuthentication) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@@ -117,6 +120,7 @@ public class VelocityConfiguration implements ProxyConfig {
this.forwardingSecret = forwardingSecret; this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers; this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough; this.pingPassthrough = pingPassthrough;
this.samplePlayersInPing = samplePlayersInPing;
this.enablePlayerAddressLogging = enablePlayerAddressLogging; this.enablePlayerAddressLogging = enablePlayerAddressLogging;
this.servers = servers; this.servers = servers;
this.forcedHosts = forcedHosts; this.forcedHosts = forcedHosts;
@@ -230,6 +234,11 @@ public class VelocityConfiguration implements ProxyConfig {
valid = false; valid = false;
} }
if (advanced.commandRateLimit < 0) {
logger.error("Invalid command rate limit {}", advanced.commandRateLimit);
valid = false;
}
loadFavicon(); loadFavicon();
return valid; return valid;
@@ -351,6 +360,31 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.getReadTimeout(); return advanced.getReadTimeout();
} }
@Override
public int getCommandRatelimit() {
return advanced.getCommandRateLimit();
}
@Override
public int getTabCompleteRatelimit() {
return advanced.getTabCompleteRateLimit();
}
@Override
public int getKickAfterRateLimitedTabCompletes() {
return advanced.getKickAfterRateLimitedTabCompletes();
}
@Override
public boolean isForwardCommandsIfRateLimited() {
return advanced.isForwardCommandsIfRateLimited();
}
@Override
public int getKickAfterRateLimitedCommands() {
return advanced.getKickAfterRateLimitedCommands();
}
public boolean isProxyProtocol() { public boolean isProxyProtocol() {
return advanced.isProxyProtocol(); return advanced.isProxyProtocol();
} }
@@ -371,6 +405,10 @@ public class VelocityConfiguration implements ProxyConfig {
return pingPassthrough; return pingPassthrough;
} }
public boolean getSamplePlayersInPing() {
return samplePlayersInPing;
}
public boolean isPlayerAddressLoggingEnabled() { public boolean isPlayerAddressLoggingEnabled() {
return enablePlayerAddressLogging; return enablePlayerAddressLogging;
} }
@@ -407,6 +445,10 @@ public class VelocityConfiguration implements ProxyConfig {
return forceKeyAuthentication; return forceKeyAuthentication;
} }
public boolean isEnableReusePort() {
return advanced.isEnableReusePort();
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@@ -473,7 +515,7 @@ public class VelocityConfiguration implements ProxyConfig {
String forwardingSecretString = System.getenv().getOrDefault( String forwardingSecretString = System.getenv().getOrDefault(
"VELOCITY_FORWARDING_SECRET", ""); "VELOCITY_FORWARDING_SECRET", "");
if (forwardingSecretString.isEmpty()) { if (forwardingSecretString.isBlank()) {
final String forwardSecretFile = config.get("forwarding-secret-file"); final String forwardSecretFile = config.get("forwarding-secret-file");
final Path secretPath = forwardSecretFile == null final Path secretPath = forwardSecretFile == null
? defaultForwardingSecretPath ? defaultForwardingSecretPath
@@ -486,7 +528,11 @@ public class VelocityConfiguration implements ProxyConfig {
"The file " + forwardSecretFile + " is not a valid file or it is a directory."); "The file " + forwardSecretFile + " is not a valid file or it is a directory.");
} }
} else { } else {
throw new RuntimeException("The forwarding-secret-file does not exist."); Files.createFile(secretPath);
Files.writeString(secretPath, forwardingSecretString = generateRandomString(12),
StandardCharsets.UTF_8);
logger.info("The forwarding-secret-file does not exist. A new file has been created at {}",
forwardSecretFile);
} }
} }
final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8); final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
@@ -503,6 +549,8 @@ public class VelocityConfiguration implements ProxyConfig {
final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
PingPassthroughMode.DISABLED); PingPassthroughMode.DISABLED);
final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false);
final String bind = config.getOrElse("bind", "0.0.0.0:25565"); final String bind = config.getOrElse("bind", "0.0.0.0:25565");
final int maxPlayers = config.getIntOrElse("show-max-players", 500); final int maxPlayers = config.getIntOrElse("show-max-players", 500);
final boolean onlineMode = config.getOrElse("online-mode", true); final boolean onlineMode = config.getOrElse("online-mode", true);
@@ -533,6 +581,7 @@ public class VelocityConfiguration implements ProxyConfig {
forwardingSecret, forwardingSecret,
kickExisting, kickExisting,
pingPassthroughMode, pingPassthroughMode,
samplePlayersInPing,
enablePlayerAddressLogging, enablePlayerAddressLogging,
new Servers(serversConfig), new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig), new ForcedHosts(forcedHostsConfig),
@@ -716,6 +765,18 @@ public class VelocityConfiguration implements ProxyConfig {
private boolean logPlayerConnections = true; private boolean logPlayerConnections = true;
@Expose @Expose
private boolean acceptTransfers = false; private boolean acceptTransfers = false;
@Expose
private boolean enableReusePort = false;
@Expose
private int commandRateLimit = 50;
@Expose
private boolean forwardCommandsIfRateLimited = true;
@Expose
private int kickAfterRateLimitedCommands = 5;
@Expose
private int tabCompleteRateLimit = 50;
@Expose
private int kickAfterRateLimitedTabCompletes = 10;
private Advanced() { private Advanced() {
} }
@@ -741,6 +802,12 @@ public class VelocityConfiguration implements ProxyConfig {
this.logCommandExecutions = config.getOrElse("log-command-executions", false); this.logCommandExecutions = config.getOrElse("log-command-executions", false);
this.logPlayerConnections = config.getOrElse("log-player-connections", true); this.logPlayerConnections = config.getOrElse("log-player-connections", true);
this.acceptTransfers = config.getOrElse("accepts-transfers", false); this.acceptTransfers = config.getOrElse("accepts-transfers", false);
this.enableReusePort = config.getOrElse("enable-reuse-port", false);
this.commandRateLimit = config.getIntOrElse("command-rate-limit", 25);
this.forwardCommandsIfRateLimited = config.getOrElse("forward-commands-if-rate-limited", true);
this.kickAfterRateLimitedCommands = config.getIntOrElse("kick-after-rate-limited-commands", 0);
this.tabCompleteRateLimit = config.getIntOrElse("tab-complete-rate-limit", 10); // very lenient
this.kickAfterRateLimitedTabCompletes = config.getIntOrElse("kick-after-rate-limited-tab-completes", 0);
} }
} }
@@ -804,6 +871,30 @@ public class VelocityConfiguration implements ProxyConfig {
return this.acceptTransfers; return this.acceptTransfers;
} }
public boolean isEnableReusePort() {
return enableReusePort;
}
public int getCommandRateLimit() {
return commandRateLimit;
}
public boolean isForwardCommandsIfRateLimited() {
return forwardCommandsIfRateLimited;
}
public int getKickAfterRateLimitedCommands() {
return kickAfterRateLimitedCommands;
}
public int getTabCompleteRateLimit() {
return tabCompleteRateLimit;
}
public int getKickAfterRateLimitedTabCompletes() {
return kickAfterRateLimitedTabCompletes;
}
@Override @Override
public String toString() { public String toString() {
return "Advanced{" return "Advanced{"
@@ -821,6 +912,7 @@ public class VelocityConfiguration implements ProxyConfig {
+ ", logCommandExecutions=" + logCommandExecutions + ", logCommandExecutions=" + logCommandExecutions
+ ", logPlayerConnections=" + logPlayerConnections + ", logPlayerConnections=" + logPlayerConnections
+ ", acceptTransfers=" + acceptTransfers + ", acceptTransfers=" + acceptTransfers
+ ", enableReusePort=" + enableReusePort
+ '}'; + '}';
} }
} }

View File

@@ -46,6 +46,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler; import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler; import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler;
@@ -84,6 +85,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
private static final Logger logger = LogManager.getLogger(MinecraftConnection.class); private static final Logger logger = LogManager.getLogger(MinecraftConnection.class);
private final Channel channel; private final Channel channel;
public boolean pendingConfigurationSwitch = false;
private SocketAddress remoteAddress; private SocketAddress remoteAddress;
private StateRegistry state; private StateRegistry state;
private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers; private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers;
@@ -367,6 +369,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
ensureInEventLoop(); ensureInEventLoop();
this.state = state; this.state = state;
final MinecraftVarintFrameDecoder frameDecoder = this.channel.pipeline()
.get(MinecraftVarintFrameDecoder.class);
if (frameDecoder != null) {
frameDecoder.setState(state);
}
// If the connection is LEGACY (<1.6), the decoder and encoder are not set. // If the connection is LEGACY (<1.6), the decoder and encoder are not set.
final MinecraftEncoder minecraftEncoder = this.channel.pipeline() final MinecraftEncoder minecraftEncoder = this.channel.pipeline()
.get(MinecraftEncoder.class); .get(MinecraftEncoder.class);

View File

@@ -23,7 +23,11 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DialogClearPacket;
import com.velocitypowered.proxy.protocol.packet.DialogShowPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
@@ -48,6 +52,7 @@ import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@@ -67,6 +72,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerComma
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -364,4 +371,32 @@ public interface MinecraftSessionHandler {
default boolean handle(ClientboundServerLinksPacket packet) { default boolean handle(ClientboundServerLinksPacket packet) {
return false; return false;
} }
default boolean handle(DialogClearPacket packet) {
return false;
}
default boolean handle(DialogShowPacket packet) {
return false;
}
default boolean handle(ServerboundCustomClickActionPacket packet) {
return false;
}
default boolean handle(CodeOfConductPacket packet) {
return false;
}
default boolean handle(CodeOfConductAcceptPacket packet) {
return false;
}
default boolean handle(ClientboundSoundEntityPacket packet) {
return false;
}
default boolean handle(ClientboundStopSoundPacket packet) {
return false;
}
} }

View File

@@ -49,6 +49,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
@@ -151,6 +152,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection smc = serverConn.ensureConnected(); MinecraftConnection smc = serverConn.ensureConnected();
smc.setAutoReading(false); smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state // Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.CONFIG);
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG);
serverConn.getPlayer().switchToConfigState(); serverConn.getPlayer().switchToConfigState();
return true; return true;
@@ -177,11 +179,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(BossBarPacket packet) { public boolean handle(BossBarPacket packet) {
if (serverConn.getPlayer().getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
if (packet.getAction() == BossBarPacket.ADD) { if (packet.getAction() == BossBarPacket.ADD) {
playerSessionHandler.getServerBossBars().add(packet.getUuid()); playerSessionHandler.getServerBossBars().add(packet.getUuid());
} else if (packet.getAction() == BossBarPacket.REMOVE) { } else if (packet.getAction() == BossBarPacket.REMOVE) {
playerSessionHandler.getServerBossBars().remove(packet.getUuid()); playerSessionHandler.getServerBossBars().remove(packet.getUuid());
} }
}
return false; // forward return false; // forward
} }
@@ -342,8 +346,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
// Inject commands from the proxy. // Inject commands from the proxy.
final CommandGraphInjector<CommandSource> injector = server.getCommandManager().getInjector(); final CommandGraphInjector<CommandSource> injector = server.getCommandManager().getInjector();
injector.inject(rootNode, serverConn.getPlayer()); injector.inject(rootNode, serverConn.getPlayer());
// In 1.21.6 a confirmation prompt was added when executing a command via `run_command` click
// action if the command is unknown. To prevent this prompt we have to send the command.
if (this.playerConnection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_6)) {
rootNode.removeChildByName("velocity:callback"); rootNode.removeChildByName("velocity:callback");
} }
}
server.getEventManager().fire( server.getEventManager().fire(
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode)) new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
@@ -301,9 +302,24 @@ public class BungeeCordMessageResponder {
} }
} }
static String getBungeeCordChannel(ProtocolVersion version) { private void processGetPlayerServer(ByteBufDataInput in) {
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL.getId() proxy.getPlayer(in.readUTF()).ifPresent(player -> {
: LEGACY_CHANNEL.getId(); player.getCurrentServer().ifPresent(server -> {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("GetPlayerServer");
out.writeUTF(player.getUsername());
out.writeUTF(server.getServerInfo().getName());
sendResponseOnConnection(buf);
});
});
}
static ChannelIdentifier getBungeeCordChannel(ProtocolVersion version) {
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL
: LEGACY_CHANNEL;
} }
// Note: this method will always release the buffer! // Note: this method will always release the buffer!
@@ -314,8 +330,8 @@ public class BungeeCordMessageResponder {
// Note: this method will always release the buffer! // Note: this method will always release the buffer!
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) { private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected(); MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion()); ChannelIdentifier chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
PluginMessagePacket msg = new PluginMessagePacket(chan, buf); PluginMessagePacket msg = new PluginMessagePacket(chan.getId(), buf);
serverConnection.write(msg); serverConnection.write(msg);
} }
@@ -331,6 +347,9 @@ public class BungeeCordMessageResponder {
ByteBufDataInput in = new ByteBufDataInput(message.content()); ByteBufDataInput in = new ByteBufDataInput(message.content());
String subChannel = in.readUTF(); String subChannel = in.readUTF();
switch (subChannel) { switch (subChannel) {
case "GetPlayerServer":
this.processGetPlayerServer(in);
break;
case "ForwardToPlayer": case "ForwardToPlayer":
this.processForwardToPlayer(in); this.processForwardToPlayer(in);
break; break;

View File

@@ -17,6 +17,7 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PreTransferEvent; import com.velocitypowered.api.event.connection.PreTransferEvent;
import com.velocitypowered.api.event.player.CookieRequestEvent; import com.velocitypowered.api.event.player.CookieRequestEvent;
import com.velocitypowered.api.event.player.CookieStoreEvent; import com.velocitypowered.api.event.player.CookieStoreEvent;
@@ -24,6 +25,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.ServerResourcePackRemoveEvent; import com.velocitypowered.api.event.player.ServerResourcePackRemoveEvent;
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
@@ -38,6 +40,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
@@ -49,11 +52,14 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -228,6 +234,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
final ConnectedPlayer player = serverConn.getPlayer(); final ConnectedPlayer player = serverConn.getPlayer();
final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.PLAY);
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
//noinspection DataFlowIssue //noinspection DataFlowIssue
configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> { configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> {
@@ -250,7 +257,13 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(DisconnectPacket packet) { public boolean handle(DisconnectPacket packet) {
serverConn.disconnect(); serverConn.disconnect();
// If the player receives a DisconnectPacket without a connection to a server in progress,
// it means that the backend server has kicked the player during reconfiguration
if (serverConn.getPlayer().getConnectionInFlight() != null) {
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer())); resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
} else {
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet, true);
}
return true; return true;
} }
@@ -261,7 +274,29 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
serverConn.getPlayer().getProtocolVersion())); serverConn.getPlayer().getProtocolVersion()));
} else { } else {
byte[] bytes = ByteBufUtil.getBytes(packet.content());
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
serverConn.getPlayer().getConnection().write(packet.retain()); serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}
// Handling this stuff async means that we should probably pause
// the connection while we toss this off into another pool
this.serverConn.getConnection().setAutoReading(false);
this.server.getEventManager()
.fire(new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, bytes))
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && !serverConn.getPlayer().getConnection().isClosed()) {
serverConn.getPlayer().getConnection().write(new PluginMessagePacket(
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
}
this.serverConn.getConnection().setAutoReading(true);
}, serverConn.ensureConnected().eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling plugin message {}", packet, ex);
return null;
});
} }
return true; return true;
} }
@@ -330,6 +365,12 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
@Override
public boolean handle(CodeOfConductPacket packet) {
this.serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}
@Override @Override
public void disconnected() { public void disconnected() {
resultFuture.completeExceptionally( resultFuture.completeExceptionally(

View File

@@ -165,7 +165,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
smc.setAutoReading(false); smc.setAutoReading(false);
clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); clientPlaySessionHandler.doSwitch().thenRunAsync(() -> smc.setAutoReading(true), smc.eventLoop());
} else { } else {
// Initial login - the player is already in configuration state. // Initial login - the player is already in configuration state.
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn)); server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn));

View File

@@ -53,6 +53,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -70,6 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private boolean gracefulDisconnect = false; private boolean gracefulDisconnect = false;
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
private final Map<Long, Long> pendingPings = new HashMap<>(); private final Map<Long, Long> pendingPings = new HashMap<>();
private @MonotonicNonNull Integer entityId;
/** /**
* Initializes a new server connection. * Initializes a new server connection.
@@ -324,6 +326,14 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
return pendingPings; return pendingPings;
} }
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
}
/** /**
* Ensures that this server connection remains "active": the connection is established and not * Ensures that this server connection remains "active": the connection is established and not
* closed, the player is still connected to the server, and the player still remains online. * closed, the player is still connected to the server, and the player still remains online.

View File

@@ -97,7 +97,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// Initiate a regular connection and move over to it. // Initiate a regular connection and move over to it.
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode, mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
inbound.getIdentifiedKey()); inbound.getHandshakeIntent(), inbound.getIdentifiedKey());
this.connectedPlayer = player; this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) { if (!server.canRegisterConnection(player)) {
player.disconnect0( player.disconnect0(
@@ -106,7 +106,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
if (server.getConfiguration().isLogPlayerConnections()) {
logger.info("{} has connected", player); logger.info("{} has connected", player);
}
return server.getEventManager() return server.getEventManager()
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) .fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))

View File

@@ -17,14 +17,17 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent;
import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent;
import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
@@ -37,10 +40,13 @@ import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -123,8 +129,32 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
brandChannel = packet.getChannel(); brandChannel = packet.getChannel();
// Client sends `minecraft:brand` packet immediately after Login, // Client sends `minecraft:brand` packet immediately after Login,
// but at this time the backend server may not be ready // but at this time the backend server may not be ready
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
return true;
} else if (serverConn != null) { } else if (serverConn != null) {
byte[] bytes = ByteBufUtil.getBytes(packet.content());
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
serverConn.ensureConnected().write(packet.retain()); serverConn.ensureConnected().write(packet.retain());
return true;
}
// Handling this stuff async means that we should probably pause
// the connection while we toss this off into another pool
serverConn.getPlayer().getConnection().setAutoReading(false);
this.server.getEventManager()
.fire(new PluginMessageEvent(serverConn.getPlayer(), serverConn, id, bytes))
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && serverConn.getConnection() != null) {
serverConn.ensureConnected().write(new PluginMessagePacket(
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
}
serverConn.getPlayer().getConnection().setAutoReading(true);
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling plugin message packet for {}", player, ex);
return null;
});
} }
return true; return true;
} }
@@ -142,7 +172,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(KnownPacksPacket packet) { public boolean handle(KnownPacksPacket packet) {
callConfigurationEvent().thenRun(() -> { callConfigurationEvent().thenRun(() -> {
player.getConnectionInFlightOrConnectedServer().ensureConnected().write(packet); VelocityServerConnection targetServer =
player.getConnectionInFlightOrConnectedServer();
if (targetServer != null) {
targetServer.ensureConnected().write(packet);
}
}).exceptionally(ex -> { }).exceptionally(ex -> {
logger.error("Error forwarding known packs response to backend:", ex); logger.error("Error forwarding known packs response to backend:", ex);
return null; return null;
@@ -173,6 +207,26 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
@Override
public boolean handle(ServerboundCustomClickActionPacket packet) {
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet.retain());
return true;
}
return false;
}
@Override
public boolean handle(CodeOfConductAcceptPacket packet) {
if (this.player.getConnectionInFlight() != null) {
this.player.getConnectionInFlight().ensureConnected().write(packet);
return true;
}
return false;
}
@Override @Override
public void handleGeneric(MinecraftPacket packet) { public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();

View File

@@ -21,7 +21,6 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.suggestion.Suggestion;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
@@ -74,6 +73,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil; import com.velocitypowered.proxy.util.CharacterUtil;
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@@ -88,6 +88,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -113,6 +114,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private CompletableFuture<Void> configSwitchFuture; private CompletableFuture<Void> configSwitchFuture;
private int failedTabCompleteAttempts;
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
* *
@@ -160,7 +163,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
configSwitchFuture = new CompletableFuture<>(); configSwitchFuture = new CompletableFuture<>();
Collection<String> channels = Collection<ChannelIdentifier> channels =
server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion()); server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
PluginMessagePacket register = constructChannelsPacket(player.getProtocolVersion(), channels); PluginMessagePacket register = constructChannelsPacket(player.getProtocolVersion(), channels);
@@ -170,6 +173,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void deactivated() { public void deactivated() {
player.discardChatQueue();
for (PluginMessagePacket message : loginPluginMessages) { for (PluginMessagePacket message : loginPluginMessages) {
ReferenceCountUtil.release(message); ReferenceCountUtil.release(message);
} }
@@ -307,20 +311,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
logger.warn("A plugin message was received while the backend server was not " logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel()); + "ready. Channel: {}. Packet discarded.", packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) { } else if (PluginMessageUtil.isRegister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<ChannelIdentifier> channels =
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>(); PluginMessageUtil.getChannels(this.player.getClientsideChannels().size(), packet,
for (String channel : channels) { this.player.getProtocolVersion());
try { player.getClientsideChannels().addAll(channels);
channelIdentifiers.add(MinecraftChannelIdentifier.from(channel));
} catch (IllegalArgumentException e) {
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
}
}
server.getEventManager() server.getEventManager()
.fireAndForget( .fireAndForget(
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers))); new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channels)));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) { } else if (PluginMessageUtil.isUnregister(packet)) {
player.getClientsideChannels()
.removeAll(PluginMessageUtil.getChannels(0, packet, this.player.getProtocolVersion()));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isMcBrand(packet)) { } else if (PluginMessageUtil.isMcBrand(packet)) {
String brand = PluginMessageUtil.readBrandMessage(packet.content()); String brand = PluginMessageUtil.readBrandMessage(packet.content());
@@ -376,10 +377,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(FinishedUpdatePacket packet) { public boolean handle(FinishedUpdatePacket packet) {
if (!player.getConnection().pendingConfigurationSwitch) {
throw new QuietRuntimeException("Not expecting reconfiguration");
}
// Complete client switch // Complete client switch
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG); player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection)); server.getEventManager()
.fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection));
if (serverConnection != null) { if (serverConnection != null) {
MinecraftConnection smc = serverConnection.ensureConnected(); MinecraftConnection smc = serverConnection.ensureConnected();
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@@ -426,6 +431,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
@Override
public boolean handle(JoinGamePacket packet) {
// Forward the packet as normal, but discard any chat state we have queued - the client will do this too
player.discardChatQueue();
return false;
}
@Override @Override
public void handleGeneric(MinecraftPacket packet) { public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
@@ -507,9 +519,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Config state clears everything in the client. No need to clear later. // Config state clears everything in the client. No need to clear later.
spawned = false; spawned = false;
serverBossBars.clear();
player.clearPlayerListHeaderAndFooterSilent(); player.clearPlayerListHeaderAndFooterSilent();
player.getTabList().clearAllSilent(); player.getTabList().clearAllSilent();
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
player.getBossBarManager().dropPackets();
} else {
serverBossBars.clear();
}
} }
player.switchToConfigState(); player.switchToConfigState();
@@ -547,8 +563,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to destination.setEntityId(joinGame.getEntityId()); // used for sound api
// track them. if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
player.getBossBarManager().sendBossBars();
} else {
// Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2),
// thus the need to track them.
for (UUID serverBossBar : serverBossBars) { for (UUID serverBossBar : serverBossBars) {
BossBarPacket deletePacket = new BossBarPacket(); BossBarPacket deletePacket = new BossBarPacket();
deletePacket.setUuid(serverBossBar); deletePacket.setUuid(serverBossBar);
@@ -556,14 +576,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().delayedWrite(deletePacket); player.getConnection().delayedWrite(deletePacket);
} }
serverBossBars.clear(); serverBossBars.clear();
}
// Tell the server about the proxy's plugin message channels. // Tell the server about the proxy's plugin message channels.
ProtocolVersion serverVersion = serverMc.getProtocolVersion(); ProtocolVersion serverVersion = serverMc.getProtocolVersion();
final Collection<String> channels = server.getChannelRegistrar() final Collection<ChannelIdentifier> channels = server.getChannelRegistrar()
.getChannelsForProtocol(serverMc.getProtocolVersion()); .getChannelsForProtocol(serverMc.getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels)); serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels));
} }
// Tell the server about this client's plugin message channels.
if (!player.getClientsideChannels().isEmpty()) {
serverMc.delayedWrite(constructChannelsPacket(serverVersion, player.getClientsideChannels()));
}
// If we had plugin messages queued during login/FML handshake, send them now. // If we had plugin messages queued during login/FML handshake, send them now.
PluginMessagePacket pm; PluginMessagePacket pm;
@@ -645,29 +670,52 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return false; return false;
} }
if (!server.getTabCompleteRateLimiter().attempt(player.getUniqueId())) {
if (server.getConfiguration().isKickOnTabCompleteRateLimit()
&& failedTabCompleteAttempts++ >= server.getConfiguration().getKickAfterRateLimitedTabCompletes()) {
player.disconnect(Component.translatable("velocity.kick.tab-complete-rate-limit"));
}
return true;
}
failedTabCompleteAttempts = 0;
server.getCommandManager().offerBrigadierSuggestions(player, command) server.getCommandManager().offerBrigadierSuggestions(player, command)
.thenAcceptAsync(suggestions -> { .thenAcceptAsync(suggestions -> {
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) {
return; return;
} }
int startPos = -1;
for (var suggestion : suggestions.getList()) {
if (startPos == -1 || startPos > suggestion.getRange().getStart()) {
startPos = suggestion.getRange().getStart();
}
}
if (startPos > 0) {
List<Offer> offers = new ArrayList<>(); List<Offer> offers = new ArrayList<>();
for (Suggestion suggestion : suggestions.getList()) { for (Suggestion suggestion : suggestions.getList()) {
String offer = suggestion.getText(); String offer;
if (suggestion.getRange().getStart() == startPos) {
offer = suggestion.getText();
} else {
offer = command.substring(startPos, suggestion.getRange().getStart()) + suggestion.getText();
}
ComponentHolder tooltip = null; ComponentHolder tooltip = null;
if (suggestion.getTooltip() != null if (suggestion.getTooltip() instanceof ComponentLike componentLike) {
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) { tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent());
tooltip = new ComponentHolder(player.getProtocolVersion(), } else if (suggestion.getTooltip() != null) {
((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString()));
} }
offers.add(new Offer(offer, tooltip)); offers.add(new Offer(offer, tooltip));
} }
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
if (startPos > 0) {
TabCompleteResponsePacket resp = new TabCompleteResponsePacket(); TabCompleteResponsePacket resp = new TabCompleteResponsePacket();
resp.setTransactionId(packet.getTransactionId()); resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos); resp.setStart(startPos + 1);
resp.setLength(packet.getCommand().length() - startPos); resp.setLength(packet.getCommand().length() - startPos - 1);
resp.getOffers().addAll(offers); resp.getOffers().addAll(offers);
player.getConnection().write(resp); player.getConnection().write(resp);
} }
@@ -722,10 +770,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
offer = offer.substring(command.length()); offer = offer.substring(command.length());
} }
ComponentHolder tooltip = null; ComponentHolder tooltip = null;
if (suggestion.getTooltip() != null if (suggestion.getTooltip() instanceof ComponentLike componentLike) {
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) { tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent());
tooltip = new ComponentHolder(player.getProtocolVersion(), } else if (suggestion.getTooltip() != null) {
((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent()); tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString()));
} }
response.getOffers().add(new Offer(offer, tooltip)); response.getOffers().add(new Offer(offer, tooltip));
} }

View File

@@ -38,6 +38,7 @@ import com.velocitypowered.api.event.player.PlayerModInfoEvent;
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
import com.velocitypowered.api.network.HandshakeIntent;
import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
@@ -61,6 +62,7 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.bossbar.BossBarManager;
import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler; import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler;
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
@@ -72,6 +74,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
@@ -98,6 +102,7 @@ import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.DurationUtils; import com.velocitypowered.proxy.util.DurationUtils;
import com.velocitypowered.proxy.util.TranslatableMapper; import com.velocitypowered.proxy.util.TranslatableMapper;
import com.velocitypowered.proxy.util.collect.CappedSet;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -121,9 +126,12 @@ import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.platform.facet.FacetPointers; import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.resource.ResourcePackInfoLike; import net.kyori.adventure.resource.ResourcePackInfoLike;
import net.kyori.adventure.resource.ResourcePackRequest; import net.kyori.adventure.resource.ResourcePackRequest;
import net.kyori.adventure.resource.ResourcePackRequestLike; import net.kyori.adventure.resource.ResourcePackRequestLike;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
@@ -143,19 +151,30 @@ import org.jetbrains.annotations.NotNull;
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable, public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
VelocityInboundConnection { VelocityInboundConnection {
public static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = Integer.getInteger("velocity.max-clientside-plugin-channels", 1024);
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build(); PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
private static final ComponentLogger logger = ComponentLogger.logger(ConnectedPlayer.class); private static final ComponentLogger logger = ComponentLogger.logger(ConnectedPlayer.class);
private final Identity identity = new IdentityImpl(); private static final @NotNull PointersSupplier<ConnectedPlayer> POINTERS_SUPPLIER =
PointersSupplier.<ConnectedPlayer>builder()
.resolving(Identity.UUID, Player::getUniqueId)
.resolving(Identity.NAME, Player::getUsername)
.resolving(Identity.DISPLAY_NAME, player -> Component.text(player.getUsername()))
.resolving(Identity.LOCALE, Player::getEffectiveLocale)
.resolving(PermissionChecker.POINTER, Player::getPermissionChecker)
.resolving(FacetPointers.TYPE, player -> Type.PLAYER)
.build();
/** /**
* The actual Minecraft connection. This is actually a wrapper object around the Netty channel. * The actual Minecraft connection. This is actually a wrapper object around the Netty channel.
*/ */
private final MinecraftConnection connection; private final MinecraftConnection connection;
private final @Nullable InetSocketAddress virtualHost; private final @Nullable InetSocketAddress virtualHost;
private final @Nullable String rawVirtualHost; private final @Nullable String rawVirtualHost;
private final HandshakeIntent handshakeIntent;
private GameProfile profile; private GameProfile profile;
private PermissionFunction permissionFunction; private PermissionFunction permissionFunction;
private int tryIndex = 0; private int tryIndex = 0;
@@ -171,37 +190,33 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private final InternalTabList tabList; private final InternalTabList tabList;
private final VelocityServer server; private final VelocityServer server;
private ClientConnectionPhase connectionPhase; private ClientConnectionPhase connectionPhase;
private final Collection<ChannelIdentifier> clientsideChannels;
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>(); private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
private @MonotonicNonNull List<String> serversToTry = null; private @MonotonicNonNull List<String> serversToTry = null;
private final ResourcePackHandler resourcePackHandler; private final ResourcePackHandler resourcePackHandler;
private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this); private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this);
private final @NotNull Pointers pointers =
Player.super.pointers().toBuilder()
.withDynamic(Identity.UUID, this::getUniqueId)
.withDynamic(Identity.NAME, this::getUsername)
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
.withDynamic(Identity.LOCALE, this::getEffectiveLocale)
.withStatic(PermissionChecker.POINTER, getPermissionChecker())
.withStatic(FacetPointers.TYPE, Type.PLAYER).build();
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private final @Nullable IdentifiedKey playerKey; private final @Nullable IdentifiedKey playerKey;
private @Nullable ClientSettingsPacket clientSettingsPacket; private @Nullable ClientSettingsPacket clientSettingsPacket;
private final ChatQueue chatQueue; private volatile ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory; private final ChatBuilderFactory chatBuilderFactory;
private final BossBarManager bossBarManager;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode, @Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
@Nullable IdentifiedKey playerKey) { HandshakeIntent handshakeIntent, @Nullable IdentifiedKey playerKey) {
this.server = server; this.server = server;
this.profile = profile; this.profile = profile;
this.connection = connection; this.connection = connection;
this.virtualHost = virtualHost; this.virtualHost = virtualHost;
this.rawVirtualHost = rawVirtualHost; this.rawVirtualHost = rawVirtualHost;
this.handshakeIntent = handshakeIntent;
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
this.connectionPhase = connection.getType().getInitialClientPhase(); this.connectionPhase = connection.getType().getInitialClientPhase();
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
this.clientsideChannels = CappedSet.create(MAX_CLIENTSIDE_PLUGIN_CHANNELS);
if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
this.tabList = new VelocityTabList(this); this.tabList = new VelocityTabList(this);
@@ -214,6 +229,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.chatQueue = new ChatQueue(this); this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion()); this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
this.resourcePackHandler = ResourcePackHandler.create(this, server); this.resourcePackHandler = ResourcePackHandler.create(this, server);
this.bossBarManager = new BossBarManager(this);
} }
/** /**
@@ -233,13 +249,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return chatQueue; return chatQueue;
} }
/**
* Discards any messages still being processed by the {@link ChatQueue}, and creates a fresh state for future packets.
* This should be used on server switches, or whenever the client resets its own 'last seen' state.
*/
public void discardChatQueue() {
// No need for atomic swap, should only be called from event loop
final ChatQueue oldChatQueue = chatQueue;
chatQueue = new ChatQueue(this);
oldChatQueue.close();
}
public BundleDelimiterHandler getBundleHandler() { public BundleDelimiterHandler getBundleHandler() {
return this.bundleHandler; return this.bundleHandler;
} }
@Override @Override
public @NonNull Identity identity() { public @NonNull Identity identity() {
return this.identity; return Identity.identity(this.getUniqueId());
} }
@Override @Override
@@ -345,7 +372,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public @NotNull Pointers pointers() { public @NotNull Pointers pointers() {
return this.pointers; return POINTERS_SUPPLIER.view(this);
} }
@Override @Override
@@ -378,14 +405,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
/** /**
* Translates the message in the user's locale. * Translates the message in the user's locale, falling back to the default locale if not set.
* *
* @param message the message to translate * @param message the message to translate
* @return the translated message * @return the translated message
*/ */
public Component translateMessage(Component message) { public Component translateMessage(Component message) {
Locale locale = ClosestLocaleMatcher.INSTANCE Locale locale = this.getEffectiveLocale();
.lookupClosest(getEffectiveLocale() == null ? Locale.getDefault() : getEffectiveLocale()); if (locale == null && settings != null) {
locale = settings.getLocale();
}
if (locale == null) {
locale = Locale.getDefault();
}
locale = ClosestLocaleMatcher.INSTANCE.lookupClosest(locale);
return GlobalTranslator.render(message, locale); return GlobalTranslator.render(message, locale);
} }
@@ -707,15 +740,19 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
Component disconnectReason = disconnect.getReason().getComponent(); Component disconnectReason = disconnect.getReason().getComponent();
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
if (this.server.getConfiguration().isLogPlayerConnections()) {
logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
plainTextReason); plainTextReason);
}
handleConnectionException(server, disconnectReason, handleConnectionException(server, disconnectReason,
Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED, Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED,
Component.text(server.getServerInfo().getName()), Component.text(server.getServerInfo().getName()),
disconnectReason), safe); disconnectReason), safe);
} else { } else {
if (this.server.getConfiguration().isLogPlayerConnections()) {
logger.error("{}: disconnected while connecting to {}: {}", this, logger.error("{}: disconnected while connecting to {}: {}", this,
server.getServerInfo().getName(), plainTextReason); server.getServerInfo().getName(), plainTextReason);
}
handleConnectionException(server, disconnectReason, handleConnectionException(server, disconnectReason,
Component.translatable("velocity.error.cant-connect", NamedTextColor.RED, Component.translatable("velocity.error.cant-connect", NamedTextColor.RED,
Component.text(server.getServerInfo().getName()), Component.text(server.getServerInfo().getName()),
@@ -780,9 +817,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
createConnectionRequest(res.getServer(), previousConnection).connect() createConnectionRequest(res.getServer(), previousConnection).connect()
.whenCompleteAsync((status, throwable) -> { .whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
handleConnectionException( handleConnectionException(res.getServer(), throwable, true);
status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
true);
return; return;
} }
@@ -1012,6 +1047,50 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.clientBrand = clientBrand; this.clientBrand = clientBrand;
} }
@Override
public void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) {
Preconditions.checkNotNull(sound, "sound");
Preconditions.checkNotNull(emitter, "emitter");
VelocityServerConnection soundTargetServerConn = getConnectedServer();
if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3)
|| connection.getState() != StateRegistry.PLAY
|| soundTargetServerConn == null
|| (sound.source() == Sound.Source.UI
&& getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) {
return;
}
VelocityServerConnection soundEmitterServerConn;
if (emitter == Sound.Emitter.self()) {
soundEmitterServerConn = soundTargetServerConn;
} else if (emitter instanceof ConnectedPlayer player) {
if ((soundEmitterServerConn = player.getConnectedServer()) == null) {
return;
}
if (!soundEmitterServerConn.getServer().equals(soundTargetServerConn.getServer())) {
return;
}
} else {
return;
}
connection.write(new ClientboundSoundEntityPacket(sound, null, soundEmitterServerConn.getEntityId()));
}
@Override
public void stopSound(@NotNull SoundStop stop) {
Preconditions.checkNotNull(stop, "stop");
if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3)
|| connection.getState() != StateRegistry.PLAY
|| (stop.source() == Sound.Source.UI
&& getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) {
return;
}
connection.write(new ClientboundStopSoundPacket(stop));
}
@Override @Override
public void transferToHost(final InetSocketAddress address) { public void transferToHost(final InetSocketAddress address) {
Preconditions.checkNotNull(address); Preconditions.checkNotNull(address);
@@ -1087,8 +1166,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol"); throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol");
} }
connection.write(new ClientboundServerLinksPacket(List.copyOf(links).stream() connection.write(new ClientboundServerLinksPacket(links.stream()
.map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList())); .map(l -> new ClientboundServerLinksPacket.ServerLink(
l.getBuiltInType().map(Enum::ordinal).orElse(-1),
l.getCustomLabel()
.map(c -> new ComponentHolder(getProtocolVersion(), translateMessage(c)))
.orElse(null),
l.getUrl().toString()))
.toList()));
} }
@Override @Override
@@ -1284,11 +1369,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void switchToConfigState() { public void switchToConfigState() {
server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, getConnectionInFlightOrConnectedServer())) server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, getConnectionInFlightOrConnectedServer()))
.completeOnTimeout(null, 5, TimeUnit.SECONDS).thenRunAsync(() -> { .completeOnTimeout(null, 5, TimeUnit.SECONDS).thenRunAsync(() -> {
// if the connection was closed earlier, there is a risk that the player is no longer connected
if (!connection.getChannel().isActive()) {
return;
}
if (bundleHandler.isInBundleSession()) { if (bundleHandler.isInBundleSession()) {
bundleHandler.toggleBundleSession(); bundleHandler.toggleBundleSession();
connection.write(BundleDelimiterPacket.INSTANCE); connection.write(BundleDelimiterPacket.INSTANCE);
} }
connection.write(StartUpdatePacket.INSTANCE); connection.write(StartUpdatePacket.INSTANCE);
connection.pendingConfigurationSwitch = true;
connection.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); connection.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start // Make sure we don't send any play packets to the player after update start
connection.addPlayPacketQueueHandler(); connection.addPlayPacketQueueHandler();
@@ -1317,24 +1408,34 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.connectionPhase = connectionPhase; this.connectionPhase = connectionPhase;
} }
/**
* Return all the plugin message channels that registered by client.
*
* @return the channels
*/
public Collection<ChannelIdentifier> getClientsideChannels() {
return clientsideChannels;
}
@Override @Override
public @Nullable IdentifiedKey getIdentifiedKey() { public @Nullable IdentifiedKey getIdentifiedKey() {
return playerKey; return playerKey;
} }
private class IdentityImpl implements Identity {
@Override
public @NonNull UUID uuid() {
return ConnectedPlayer.this.getUniqueId();
}
}
@Override @Override
public ProtocolState getProtocolState() { public ProtocolState getProtocolState() {
return connection.getState().toProtocolState(); return connection.getState().toProtocolState();
} }
@Override
public HandshakeIntent getHandshakeIntent() {
return handshakeIntent;
}
public BossBarManager getBossBarManager() {
return bossBarManager;
}
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final RegisteredServer toConnect; private final RegisteredServer toConnect;
@@ -1394,7 +1495,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
VelocityServerConnection con = VelocityServerConnection con =
new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server); new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server);
connectionInFlight = con; connectionInFlight = con;
return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con),
return con.connect().whenCompleteAsync((result, exception) -> {
if (result != null && !result.isSuccessful() && !result.isSafe()) {
handleConnectionException(result.getAttemptedConnection(),
// The only way for the reason to be null is if the result is safe
DisconnectPacket.create(result.getReasonComponent().orElseThrow(),
getProtocolVersion(), connection.getState()), false);
}
this.resetIfInFlightIs(con);
},
connection.eventLoop()); connection.eventLoop());
}, connection.eventLoop()); }, connection.eventLoop());
}); });
@@ -1408,22 +1518,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public CompletableFuture<Result> connect() { public CompletableFuture<Result> connect() {
return this.internalConnect().whenCompleteAsync((status, throwable) -> { return this.internalConnect().thenApply(x -> x);
if (status != null && !status.isSuccessful()) {
if (!status.isSafe()) {
handleConnectionException(status.getAttemptedConnection(), throwable, false);
}
}
}, connection.eventLoop()).thenApply(x -> x);
} }
@Override @Override
public CompletableFuture<Boolean> connectWithIndication() { public CompletableFuture<Boolean> connectWithIndication() {
return internalConnect().whenCompleteAsync((status, throwable) -> { return internalConnect().whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
// TODO: The exception handling from this is not very good. Find a better way. handleConnectionException(toConnect, throwable, true);
handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect,
throwable, true);
return; return;
} }

View File

@@ -277,5 +277,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
public ProtocolState getProtocolState() { public ProtocolState getProtocolState() {
return connection.getState().toProtocolState(); return connection.getState().toProtocolState();
} }
@Override
public HandshakeIntent getHandshakeIntent() {
return HandshakeIntent.STATUS;
}
} }
} }

View File

@@ -17,6 +17,7 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.network.HandshakeIntent;
import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
@@ -98,6 +99,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
return connection.getState().toProtocolState(); return connection.getState().toProtocolState();
} }
@Override
public HandshakeIntent getHandshakeIntent() {
return handshake.getIntent();
}
/** /**
* Disconnects the connection from the server. * Disconnects the connection from the server.
* *

View File

@@ -17,6 +17,7 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.network.HandshakeIntent;
import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.LoginPhaseConnection; import com.velocitypowered.api.proxy.LoginPhaseConnection;
@@ -177,4 +178,9 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
public ProtocolState getProtocolState() { public ProtocolState getProtocolState() {
return delegate.getProtocolState(); return delegate.getProtocolState();
} }
@Override
public HandshakeIntent getHandshakeIntent() {
return delegate.getHandshakeIntent();
}
} }

View File

@@ -64,6 +64,7 @@ class LegacyForgeUtil {
if (discriminator == MOD_LIST_DISCRIMINATOR) { if (discriminator == MOD_LIST_DISCRIMINATOR) {
ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder(); ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder();
int modCount = ProtocolUtils.readVarInt(contents); int modCount = ProtocolUtils.readVarInt(contents);
Preconditions.checkArgument(modCount < 1024, "Oversized mods list");
for (int index = 0; index < modCount; index++) { for (int index = 0; index < modCount; index++) {
String id = ProtocolUtils.readString(contents); String id = ProtocolUtils.readString(contents);

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.bossbar;
import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import java.util.HashSet;
import java.util.Set;
/**
* Handles dropping and resending boss bar packets on versions 1.20.2 and newer because the client now
* deletes all boss bars during the login phase, and sending update packets would cause the client to be disconnected.
*/
public final class BossBarManager {
private final ConnectedPlayer player;
private final Set<VelocityBossBarImplementation> bossBars = new HashSet<>();
private boolean dropPackets = false;
public BossBarManager(ConnectedPlayer player) {
this.player = player;
}
/**
* Records the specified boss bar to be re-sent when a player changes server, and sends the update packet
* if the client is able to receive it and not be disconnected.
*/
public synchronized void writeUpdate(VelocityBossBarImplementation bar, BossBarPacket packet) {
this.bossBars.add(bar);
if (!this.dropPackets) {
this.player.getConnection().write(packet);
}
}
/**
* Removes the specified boss bar from the player to ensure it is not re-sent.
*/
public synchronized void remove(VelocityBossBarImplementation bar, BossBarPacket packet) {
this.bossBars.remove(bar);
if (!this.dropPackets) {
this.player.getConnection().write(packet);
}
}
/**
* Re-creates the boss bars the player can see with any updates that may have occurred in the meantime,
* and allows update packets for those boss bars to be sent.
*/
public synchronized void sendBossBars() {
for (VelocityBossBarImplementation bossBar : bossBars) {
bossBar.createDirect(player);
}
this.dropPackets = false;
}
/**
* Prevents the player from receiving boss bar update packets while logging in to a new server.
*/
public synchronized void dropPackets() {
this.dropPackets = true;
}
}

View File

@@ -111,7 +111,7 @@ public abstract sealed class ResourcePackHandler
} }
request.setRequired(queued.getShouldForce()); request.setRequired(queued.getShouldForce());
request.setPrompt(queued.getPrompt() == null ? null : request.setPrompt(queued.getPrompt() == null ? null :
new ComponentHolder(player.getProtocolVersion(), queued.getPrompt())); new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt())));
player.getConnection().write(request); player.getConnection().write(request);
} }

View File

@@ -30,10 +30,13 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
/** /**
* Common utilities for handling server list ping results. * Common utilities for handling server list ping results.
@@ -51,11 +54,27 @@ public class ServerListPingHandler {
version = ProtocolVersion.MAXIMUM_VERSION; version = ProtocolVersion.MAXIMUM_VERSION;
} }
VelocityConfiguration configuration = server.getConfiguration(); VelocityConfiguration configuration = server.getConfiguration();
List<ServerPing.SamplePlayer> samplePlayers;
if (configuration.getSamplePlayersInPing()) {
List<ServerPing.SamplePlayer> unshuffledPlayers = server.getAllPlayers().stream()
.map(p -> {
if (p.getPlayerSettings().isClientListingAllowed()) {
return new ServerPing.SamplePlayer(p.getUsername(), p.getUniqueId());
} else {
return ServerPing.SamplePlayer.ANONYMOUS;
}
})
.collect(Collectors.toList());
Collections.shuffle(unshuffledPlayers);
samplePlayers = unshuffledPlayers.subList(0, Math.min(12, server.getPlayerCount()));
} else {
samplePlayers = ImmutableList.of();
}
return new ServerPing( return new ServerPing(
new ServerPing.Version(version.getProtocol(), new ServerPing.Version(version.getProtocol(),
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
ImmutableList.of()), samplePlayers),
configuration.getMotd(), configuration.getMotd(),
configuration.getFavicon().orElse(null), configuration.getFavicon().orElse(null),
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
@@ -63,7 +82,7 @@ public class ServerListPingHandler {
} }
private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection, private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) { PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion, String virtualHostStr) {
ServerPing fallback = constructLocalPing(connection.getProtocolVersion()); ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
List<CompletableFuture<ServerPing>> pings = new ArrayList<>(); List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
for (String s : servers) { for (String s : servers) {
@@ -73,7 +92,7 @@ public class ServerListPingHandler {
} }
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get(); VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder() pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder()
.version(responseProtocolVersion).build())); .version(responseProtocolVersion).virtualHost(virtualHostStr).build()));
} }
if (pings.isEmpty()) { if (pings.isEmpty()) {
return CompletableFuture.completedFuture(fallback); return CompletableFuture.completedFuture(fallback);
@@ -89,6 +108,13 @@ public class ServerListPingHandler {
if (response == fallback) { if (response == fallback) {
continue; continue;
} }
if (response.getDescriptionComponent() == null) {
return response.asBuilder()
.description(Component.empty())
.build();
}
return response; return response;
} }
return fallback; return fallback;
@@ -155,7 +181,7 @@ public class ServerListPingHandler {
.orElse(""); .orElse("");
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder()); virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion); return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr);
} }
} }
} }

View File

@@ -136,6 +136,10 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
if (!this.server.getCommandManager().executeAsync(this, command).join()) { if (!this.server.getCommandManager().executeAsync(this, command).join()) {
sendMessage(Component.translatable("velocity.command.command-does-not-exist", sendMessage(Component.translatable("velocity.command.command-does-not-exist",
NamedTextColor.RED)); NamedTextColor.RED));
return;
}
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("CONSOLE -> executed command /{}", command);
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("An error occurred while running this command.", e); logger.error("An error occurred while running this command.", e);

View File

@@ -23,8 +23,6 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.Key; import java.security.Key;
@@ -111,42 +109,6 @@ public enum EncryptionUtils {
} }
} }
/**
* Generates a signature for input data.
*
* @param algorithm the signature algorithm
* @param base the private key to sign with
* @param toSign the byte array(s) of data to sign
* @return the generated signature
*/
public static byte[] generateSignature(String algorithm, PrivateKey base, byte[]... toSign) {
Preconditions.checkArgument(toSign.length > 0);
try {
Signature construct = Signature.getInstance(algorithm);
construct.initSign(base);
for (byte[] bytes : toSign) {
construct.update(bytes);
}
return construct.sign();
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid signature parameters");
}
}
/**
* Encodes a long array as Big-endian byte array.
*
* @param bits the long (array) of numbers to encode
* @return the encoded bytes
*/
public static byte[] longToBigEndianByteArray(long... bits) {
ByteBuffer ret = ByteBuffer.allocate(8 * bits.length).order(ByteOrder.BIG_ENDIAN);
for (long put : bits) {
ret.putLong(put);
}
return ret.array();
}
public static String encodeUrlEncoded(byte[] data) { public static String encodeUrlEncoded(byte[] data) {
return MIME_SPECIAL_ENCODER.encodeToString(data); return MIME_SPECIAL_ENCODER.encodeToString(data);
} }
@@ -155,22 +117,6 @@ public enum EncryptionUtils {
return Base64.getMimeDecoder().decode(toParse); return Base64.getMimeDecoder().decode(toParse);
} }
/**
* Parse a cer-encoded RSA key into its key bytes.
*
* @param toParse the cer-encoded key String
* @param descriptors the type of key
* @return the parsed key bytes
*/
public static byte[] parsePemEncoded(String toParse, Pair<String, String> descriptors) {
int startIdx = toParse.indexOf(descriptors.first());
Preconditions.checkArgument(startIdx >= 0);
int firstLen = descriptors.first().length();
int endIdx = toParse.indexOf(descriptors.second(), firstLen + startIdx) + 1;
Preconditions.checkArgument(endIdx > 0);
return decodeUrlEncoded(toParse.substring(startIdx + firstLen, endIdx));
}
/** /**
* Encodes an RSA key as String cer format. * Encodes an RSA key as String cer format.
* *

View File

@@ -350,8 +350,9 @@ public class VelocityEventManager implements EventManager {
asyncType = AsyncType.ALWAYS; asyncType = AsyncType.ALWAYS;
} }
// The default value of 0 will fall back to PostOrder, the default PostOrder (NORMAL) is also 0
final short order; final short order;
if (subscribe.order() == PostOrder.CUSTOM) { if (subscribe.priority() != 0) {
order = subscribe.priority(); order = subscribe.priority();
} else { } else {
order = (short) POST_ORDER_MAP.get(subscribe.order()); order = (short) POST_ORDER_MAP.get(subscribe.order());

View File

@@ -51,7 +51,7 @@ public class BackendChannelInitializer extends ChannelInitializer<Channel> {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel ch) {
ch.pipeline() ch.pipeline()
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.CLIENTBOUND))
.addLast(READ_TIMEOUT, .addLast(READ_TIMEOUT,
new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(),
TimeUnit.MILLISECONDS)) TimeUnit.MILLISECONDS))

View File

@@ -18,6 +18,8 @@
package com.velocitypowered.proxy.network; package com.velocitypowered.proxy.network;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
import com.velocitypowered.api.event.proxy.ListenerCloseEvent; import com.velocitypowered.api.event.proxy.ListenerCloseEvent;
import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.api.network.ListenerType;
@@ -28,14 +30,17 @@ import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
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 io.netty.channel.WriteBufferWaterMark; import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.unix.UnixChannelOption;
import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.MultithreadEventExecutorGroup;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.util.HashMap; import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -49,7 +54,7 @@ public final class ConnectionManager {
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
1 << 21); 1 << 21);
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
private final Map<InetSocketAddress, Endpoint> endpoints = new HashMap<>(); private final Multimap<InetSocketAddress, Endpoint> endpoints = HashMultimap.create();
private final TransportType transportType; private final TransportType transportType;
private final EventLoopGroup bossGroup; private final EventLoopGroup bossGroup;
private final EventLoopGroup workerGroup; private final EventLoopGroup workerGroup;
@@ -93,37 +98,60 @@ public final class ConnectionManager {
public void bind(final InetSocketAddress address) { public void bind(final InetSocketAddress address) {
final ServerBootstrap bootstrap = new ServerBootstrap() final ServerBootstrap bootstrap = new ServerBootstrap()
.channelFactory(this.transportType.serverSocketChannelFactory) .channelFactory(this.transportType.serverSocketChannelFactory)
.group(this.bossGroup, this.workerGroup)
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
.childHandler(this.serverChannelInitializer.get()) .childHandler(this.serverChannelInitializer.get())
.childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.IP_TOS, 0x18) .childOption(ChannelOption.IP_TOS, 0x18)
.localAddress(address); .localAddress(address);
if (transportType.supportsTcpFastOpenServer() && server.getConfiguration().useTcpFastOpen()) { if (server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(ChannelOption.TCP_FASTOPEN, 3); bootstrap.option(ChannelOption.TCP_FASTOPEN, 3);
} }
bootstrap.bind() if (server.getConfiguration().isEnableReusePort()) {
// We don't need a boss group, since each worker will bind to the socket
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true)
.group(this.workerGroup);
} else {
bootstrap.group(this.bossGroup, this.workerGroup);
}
final int binds = server.getConfiguration().isEnableReusePort()
? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1;
for (int bind = 0; bind < binds; bind++) {
// Wait for each bind to open. If we encounter any errors, don't try to bind again.
int finalBind = bind;
ChannelFuture f = bootstrap.bind()
.addListener((ChannelFutureListener) future -> { .addListener((ChannelFutureListener) future -> {
final Channel channel = future.channel(); final Channel channel = future.channel();
if (future.isSuccess()) { if (future.isSuccess()) {
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT)); this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
LOGGER.info("Listening on {}", channel.localAddress());
if (finalBind == 0) {
// Warn people with console access that HAProxy is in use, see PR: #1436 // Warn people with console access that HAProxy is in use, see PR: #1436
if (this.server.getConfiguration().isProxyProtocol()) { if (this.server.getConfiguration().isProxyProtocol()) {
LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress()); LOGGER.warn(
"Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.",
channel.localAddress());
} }
LOGGER.info("Listening on {}", channel.localAddress());
// Fire the proxy bound event after the socket is bound // Fire the proxy bound event after the socket is bound
server.getEventManager().fireAndForget( server.getEventManager().fireAndForget(
new ListenerBoundEvent(address, ListenerType.MINECRAFT)); new ListenerBoundEvent(address, ListenerType.MINECRAFT));
}
} else { } else {
LOGGER.error("Can't bind to {}", address, future.cause()); LOGGER.error("Can't bind to {}", address, future.cause());
} }
}); });
f.syncUninterruptibly();
if (!f.isSuccess()) {
break;
}
}
} }
/** /**
@@ -169,7 +197,7 @@ public final class ConnectionManager {
this.server.getConfiguration().getConnectTimeout()) this.server.getConfiguration().getConnectTimeout())
.group(group == null ? this.workerGroup : group) .group(group == null ? this.workerGroup : group)
.resolver(this.resolver.asGroup()); .resolver(this.resolver.asGroup());
if (transportType.supportsTcpFastOpenClient() && server.getConfiguration().useTcpFastOpen()) { if (server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, true); bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
} }
return bootstrap; return bootstrap;
@@ -181,18 +209,21 @@ public final class ConnectionManager {
* @param oldBind the endpoint to close * @param oldBind the endpoint to close
*/ */
public void close(InetSocketAddress oldBind) { public void close(InetSocketAddress oldBind) {
Endpoint endpoint = endpoints.remove(oldBind); Collection<Endpoint> endpoints = this.endpoints.removeAll(oldBind);
Preconditions.checkState(!endpoints.isEmpty(), "Endpoint was not registered");
ListenerType type = endpoints.iterator().next().getType();
// Fire proxy close event to notify plugins of socket close. We block since plugins // Fire proxy close event to notify plugins of socket close. We block since plugins
// should have a chance to be notified before the server stops accepting connections. // should have a chance to be notified before the server stops accepting connections.
server.getEventManager().fire(new ListenerCloseEvent(oldBind, endpoint.getType())).join(); server.getEventManager().fire(new ListenerCloseEvent(oldBind, type)).join();
for (Endpoint endpoint : endpoints) {
Channel serverChannel = endpoint.getChannel(); Channel serverChannel = endpoint.getChannel();
Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind);
LOGGER.info("Closing endpoint {}", serverChannel.localAddress()); LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
serverChannel.close().syncUninterruptibly(); serverChannel.close().syncUninterruptibly();
} }
}
/** /**
* Closes all the currently registered endpoints. * Closes all the currently registered endpoints.
@@ -200,14 +231,17 @@ public final class ConnectionManager {
* @param interrupt should closing forward interruptions * @param interrupt should closing forward interruptions
*/ */
public void closeEndpoints(boolean interrupt) { public void closeEndpoints(boolean interrupt) {
for (final Map.Entry<InetSocketAddress, Endpoint> entry : this.endpoints.entrySet()) { for (final Map.Entry<InetSocketAddress, Collection<Endpoint>> entry : this.endpoints.asMap()
.entrySet()) {
final InetSocketAddress address = entry.getKey(); final InetSocketAddress address = entry.getKey();
final Endpoint endpoint = entry.getValue(); final Collection<Endpoint> endpoints = entry.getValue();
ListenerType type = endpoints.iterator().next().getType();
// Fire proxy close event to notify plugins of socket close. We block since plugins // Fire proxy close event to notify plugins of socket close. We block since plugins
// should have a chance to be notified before the server stops accepting connections. // should have a chance to be notified before the server stops accepting connections.
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join(); server.getEventManager().fire(new ListenerCloseEvent(address, type)).join();
for (Endpoint endpoint : endpoints) {
LOGGER.info("Closing endpoint {}", address); LOGGER.info("Closing endpoint {}", address);
if (interrupt) { if (interrupt) {
try { try {
@@ -220,6 +254,7 @@ public final class ConnectionManager {
endpoint.getChannel().close().syncUninterruptibly(); endpoint.getChannel().close().syncUninterruptibly();
} }
} }
}
this.endpoints.clear(); this.endpoints.clear();
} }

View File

@@ -58,7 +58,7 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
protected void initChannel(final Channel ch) { protected void initChannel(final Channel ch) {
ch.pipeline() ch.pipeline()
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder(ProtocolUtils.Direction.SERVERBOUND))
.addLast(READ_TIMEOUT, .addLast(READ_TIMEOUT,
new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(), new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(),
TimeUnit.MILLISECONDS)) TimeUnit.MILLISECONDS))

View File

@@ -20,27 +20,32 @@ package com.velocitypowered.proxy.network;
import com.velocitypowered.proxy.util.concurrent.VelocityNettyThreadFactory; import com.velocitypowered.proxy.util.concurrent.VelocityNettyThreadFactory;
import io.netty.channel.ChannelFactory; import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.IoHandlerFactory;
import io.netty.channel.MultiThreadIoEventLoopGroup;
import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel; import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueue; import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel; import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.kqueue.KQueueIoHandler;
import io.netty.channel.kqueue.KQueueServerSocketChannel; import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.kqueue.KQueueSocketChannel; import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.incubator.channel.uring.*; import io.netty.channel.uring.IoUring;
import io.netty.channel.uring.IoUringDatagramChannel;
import io.netty.channel.uring.IoUringIoHandler;
import io.netty.channel.uring.IoUringServerSocketChannel;
import io.netty.channel.uring.IoUringSocketChannel;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.function.BiFunction; import java.util.function.Supplier;
/** /**
* Enumerates the supported transports for Velocity. * Enumerates the supported transports for Velocity.
@@ -49,50 +54,36 @@ public enum TransportType {
NIO("NIO", NioServerSocketChannel::new, NIO("NIO", NioServerSocketChannel::new,
NioSocketChannel::new, NioSocketChannel::new,
NioDatagramChannel::new, NioDatagramChannel::new,
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type)), NioIoHandler::newFactory),
false,
false),
EPOLL("epoll", EpollServerSocketChannel::new, EPOLL("epoll", EpollServerSocketChannel::new,
EpollSocketChannel::new, EpollSocketChannel::new,
EpollDatagramChannel::new, EpollDatagramChannel::new,
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)), EpollIoHandler::newFactory),
Epoll.isTcpFastOpenServerSideAvailable(),
Epoll.isTcpFastOpenClientSideAvailable()),
IO_URING("io_uring", IOUringServerSocketChannel::new,
IOUringSocketChannel::new,
IOUringDatagramChannel::new,
(name, type) -> new IOUringEventLoopGroup(0, createThreadFactory(name, type)),
IOUring.isTcpFastOpenServerSideAvailable(),
IOUring.isTcpFastOpenClientSideAvailable()),
KQUEUE("kqueue", KQueueServerSocketChannel::new, KQUEUE("kqueue", KQueueServerSocketChannel::new,
KQueueSocketChannel::new, KQueueSocketChannel::new,
KQueueDatagramChannel::new, KQueueDatagramChannel::new,
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)), KQueueIoHandler::newFactory),
KQueue.isTcpFastOpenServerSideAvailable(), IO_URING("io_uring", IoUringServerSocketChannel::new,
KQueue.isTcpFastOpenClientSideAvailable()); IoUringSocketChannel::new,
IoUringDatagramChannel::new,
IoUringIoHandler::newFactory);
final String name; final String name;
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory; final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory;
final ChannelFactory<? extends SocketChannel> socketChannelFactory; final ChannelFactory<? extends SocketChannel> socketChannelFactory;
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory; final ChannelFactory<? extends DatagramChannel> datagramChannelFactory;
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory; final Supplier<IoHandlerFactory> ioHandlerFactorySupplier;
final boolean supportsTcpFastOpenServer;
final boolean supportsTcpFastOpenClient;
TransportType(final String name, TransportType(final String name,
final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory, final ChannelFactory<? extends ServerSocketChannel> serverSocketChannelFactory,
final ChannelFactory<? extends SocketChannel> socketChannelFactory, final ChannelFactory<? extends SocketChannel> socketChannelFactory,
final ChannelFactory<? extends DatagramChannel> datagramChannelFactory, final ChannelFactory<? extends DatagramChannel> datagramChannelFactory,
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory, final Supplier<IoHandlerFactory> ioHandlerFactorySupplier) {
final boolean supportsTcpFastOpenServer,
final boolean supportsTcpFastOpenClient) {
this.name = name; this.name = name;
this.serverSocketChannelFactory = serverSocketChannelFactory; this.serverSocketChannelFactory = serverSocketChannelFactory;
this.socketChannelFactory = socketChannelFactory; this.socketChannelFactory = socketChannelFactory;
this.datagramChannelFactory = datagramChannelFactory; this.datagramChannelFactory = datagramChannelFactory;
this.eventLoopGroupFactory = eventLoopGroupFactory; this.ioHandlerFactorySupplier = ioHandlerFactorySupplier;
this.supportsTcpFastOpenServer = supportsTcpFastOpenServer;
this.supportsTcpFastOpenClient = supportsTcpFastOpenClient;
} }
@Override @Override
@@ -100,16 +91,15 @@ public enum TransportType {
return this.name; return this.name;
} }
/**
* Creates a new event loop group for the given type.
*
* @param type the type of event loop group to create
* @return the event loop group
*/
public EventLoopGroup createEventLoopGroup(final Type type) { public EventLoopGroup createEventLoopGroup(final Type type) {
return this.eventLoopGroupFactory.apply(this.name, type); return new MultiThreadIoEventLoopGroup(
} 0, createThreadFactory(this.name, type), this.ioHandlerFactorySupplier.get());
public boolean supportsTcpFastOpenServer() {
return supportsTcpFastOpenServer;
}
public boolean supportsTcpFastOpenClient() {
return supportsTcpFastOpenClient;
} }
private static ThreadFactory createThreadFactory(final String name, final Type type) { private static ThreadFactory createThreadFactory(final String name, final Type type) {
@@ -126,7 +116,7 @@ public enum TransportType {
return NIO; return NIO;
} }
if(IOUring.isAvailable()) { if (IoUring.isAvailable() && Boolean.getBoolean("velocity.enable-iouring-transport")) {
return IO_URING; return IO_URING;
} }

View File

@@ -32,13 +32,18 @@ public interface MinecraftPacket {
boolean handle(MinecraftSessionHandler handler); boolean handle(MinecraftSessionHandler handler);
default int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) { ProtocolVersion version) {
return -1; return -1;
} }
default int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) { ProtocolVersion version) {
return 0; return 0;
} }
default int encodeSizeHint(ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
} }

View File

@@ -44,10 +44,11 @@ import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagType; import net.kyori.adventure.nbt.BinaryTagType;
import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer;
import net.kyori.option.OptionState; import net.kyori.option.OptionSchema;
/** /**
* Utilities for writing and reading data in the Minecraft protocol. * Utilities for writing and reading data in the Minecraft protocol.
@@ -60,14 +61,19 @@ public enum ProtocolUtils {
.downsampleColors() .downsampleColors()
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options( .options(
OptionState.optionState() OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// before 1.16 // before 1.16
.value(JSONOptions.EMIT_RGB, Boolean.FALSE) .value(JSONOptions.EMIT_RGB, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.LEGACY_ONLY) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.VALUE_FIELD)
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
// before 1.20.3 // before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE) .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build() .build()
) )
.build(); .build();
@@ -75,14 +81,41 @@ public enum ProtocolUtils {
GsonComponentSerializer.builder() GsonComponentSerializer.builder()
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options( .options(
OptionState.optionState() OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16 // after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE) .value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE)
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true)
// before 1.20.3 // before 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE) .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE) .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
private static final GsonComponentSerializer PRE_1_21_5_SERIALIZER =
GsonComponentSerializer.builder()
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE)
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.CAMEL_CASE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, true)
// after 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build() .build()
) )
.build(); .build();
@@ -90,14 +123,20 @@ public enum ProtocolUtils {
GsonComponentSerializer.builder() GsonComponentSerializer.builder()
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get()) .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options( .options(
OptionState.optionState() OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16 // after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE) .value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.MODERN_ONLY) .value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.SNAKE_CASE)
.value(JSONOptions.EMIT_CLICK_EVENT_TYPE, JSONOptions.ClickEventValueMode.SNAKE_CASE)
// after 1.20.3 // after 1.20.3
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE) .value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE) .value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
// after 1.21.5
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE) .value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.FALSE)
.build() .build()
) )
.build(); .build();
@@ -111,7 +150,7 @@ public enum ProtocolUtils {
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY}; BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
private static final QuietDecoderException BAD_VARINT_CACHED = private static final QuietDecoderException BAD_VARINT_CACHED =
new QuietDecoderException("Bad VarInt decoded"); new QuietDecoderException("Bad VarInt decoded");
private static final int[] VAR_INT_LENGTHS = new int[65]; private static final int[] VAR_INT_LENGTHS = new int[33];
static { static {
for (int i = 0; i <= 32; ++i) { for (int i = 0; i <= 32; ++i) {
@@ -211,16 +250,15 @@ public enum ProtocolUtils {
} }
/** /**
* Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}. * Directly encodes a 21-bit Minecraft VarInt, ready to be written with {@link ByteBuf#writeMedium(int)}.
* The upper 11 bits will be discarded. * The upper 11 bits will be discarded.
* *
* @param buf the buffer to read from * @param value the value to encode
* @param value the integer to write * @return the encoded value
*/ */
public static void write21BitVarInt(ByteBuf buf, int value) { public static int encode21BitVarInt(int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); return (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
} }
public static String readString(ByteBuf buf) { public static String readString(ByteBuf buf) {
@@ -249,12 +287,22 @@ public enum ProtocolUtils {
checkFrame(buf.isReadable(length), checkFrame(buf.isReadable(length),
"Trying to read a string that is too long (wanted %s, only have %s)", length, "Trying to read a string that is too long (wanted %s, only have %s)", length,
buf.readableBytes()); buf.readableBytes());
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); String str = buf.readString(length, StandardCharsets.UTF_8);
buf.skipBytes(length);
checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap); checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap);
return str; return str;
} }
/**
* Determines the size of the written {@code str} if encoded as a VarInt-prefixed UTF-8 string.
*
* @param str the string to write
* @return the encoded size
*/
public static int stringSizeHint(CharSequence str) {
int size = ByteBufUtil.utf8Bytes(str);
return varIntBytes(size) + size;
}
/** /**
* Writes the specified {@code str} to the {@code buf} with a VarInt prefix. * Writes the specified {@code str} to the {@code buf} with a VarInt prefix.
* *
@@ -287,6 +335,16 @@ public enum ProtocolUtils {
writeString(buf, key.asString()); writeString(buf, key.asString());
} }
/**
* Writes the key to the buffer, dropping the "minecraft:" namespace when present.
*
* @param buf the buffer to write to
* @param key the key to write
*/
public static void writeMinimalKey(ByteBuf buf, Key key) {
writeString(buf, key.asMinimalString());
}
/** /**
* Reads a standard Mojang Text namespaced:key array from the buffer. * Reads a standard Mojang Text namespaced:key array from the buffer.
* *
@@ -713,9 +771,12 @@ public enum ProtocolUtils {
* @return the appropriate {@link GsonComponentSerializer} * @return the appropriate {@link GsonComponentSerializer}
*/ */
public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) { public static GsonComponentSerializer getJsonChatSerializer(ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) {
return MODERN_SERIALIZER; return MODERN_SERIALIZER;
} }
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
return PRE_1_21_5_SERIALIZER;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
return PRE_1_20_3_SERIALIZER; return PRE_1_20_3_SERIALIZER;
} }
@@ -749,6 +810,40 @@ public enum ProtocolUtils {
return new IdentifiedKeyImpl(revision, key, expiry, signature); return new IdentifiedKeyImpl(revision, key, expiry, signature);
} }
/**
* Reads a {@link Sound.Source} from the buffer.
*
* @param buf the buffer
* @param version the protocol version
* @return the sound source
*/
public static Sound.Source readSoundSource(ByteBuf buf, ProtocolVersion version) {
int ordinal = readVarInt(buf);
if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)
&& ordinal == Sound.Source.UI.ordinal()) {
throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+");
}
return Sound.Source.values()[ordinal];
}
/**
* Writes a {@link Sound.Source} to the buffer.
*
* @param buf the buffer
* @param version the protocol version
* @param source the sound source to write
*/
public static void writeSoundSource(ByteBuf buf, ProtocolVersion version, Sound.Source source) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)
&& source == Sound.Source.UI) {
throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+");
}
writeVarInt(buf, source.ordinal());
}
/** /**
* Represents the direction in which a packet flows. * Represents the direction in which a packet flows.
*/ */

View File

@@ -39,6 +39,9 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_9;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@@ -56,7 +59,11 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DialogClearPacket;
import com.velocitypowered.proxy.protocol.packet.DialogShowPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
@@ -79,6 +86,7 @@ import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@@ -100,6 +108,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerComm
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -182,6 +192,12 @@ public enum StateRegistry {
KnownPacksPacket.class, KnownPacksPacket.class,
KnownPacksPacket::new, KnownPacksPacket::new,
map(0x07, MINECRAFT_1_20_5, false)); map(0x07, MINECRAFT_1_20_5, false));
serverbound.register(ServerboundCustomClickActionPacket.class, ServerboundCustomClickActionPacket::new,
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register(
CodeOfConductAcceptPacket.class,
() -> CodeOfConductAcceptPacket.INSTANCE,
map(0x09, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
@@ -236,6 +252,12 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_21, false)); map(0x0F, MINECRAFT_1_21, false));
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
map(0x10, MINECRAFT_1_21, false)); map(0x10, MINECRAFT_1_21, false));
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
map(0x11, MINECRAFT_1_21_6, false));
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
map(0x12, MINECRAFT_1_21_6, false));
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
map(0x13, MINECRAFT_1_21_9, false));
} }
}, },
PLAY { PLAY {
@@ -256,7 +278,8 @@ public enum StateRegistry {
map(0x09, MINECRAFT_1_19_4, false), map(0x09, MINECRAFT_1_19_4, false),
map(0x0A, MINECRAFT_1_20_2, false), map(0x0A, MINECRAFT_1_20_2, false),
map(0x0B, MINECRAFT_1_20_5, false), map(0x0B, MINECRAFT_1_20_5, false),
map(0x0D, MINECRAFT_1_21_2, false)); map(0x0D, MINECRAFT_1_21_2, false),
map(0x0E, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
LegacyChatPacket.class, LegacyChatPacket.class,
LegacyChatPacket::new, LegacyChatPacket::new,
@@ -269,7 +292,8 @@ public enum StateRegistry {
ChatAcknowledgementPacket.class, ChatAcknowledgementPacket.class,
ChatAcknowledgementPacket::new, ChatAcknowledgementPacket::new,
map(0x03, MINECRAFT_1_19_3, false), map(0x03, MINECRAFT_1_19_3, false),
map(0x04, MINECRAFT_1_21_2, false)); map(0x04, MINECRAFT_1_21_2, false),
map(0x05, MINECRAFT_1_21_6, false));
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new, serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
map(0x03, MINECRAFT_1_19, false), map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
@@ -279,16 +303,19 @@ public enum StateRegistry {
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new, serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
map(0x04, MINECRAFT_1_19_3, false), map(0x04, MINECRAFT_1_19_3, false),
map(0x05, MINECRAFT_1_20_5, false), map(0x05, MINECRAFT_1_20_5, false),
map(0x06, MINECRAFT_1_21_2, false)); map(0x06, MINECRAFT_1_21_2, false),
map(0x07, MINECRAFT_1_21_6, false));
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new, serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
map(0x04, MINECRAFT_1_20_5, false), map(0x04, MINECRAFT_1_20_5, false),
map(0x05, MINECRAFT_1_21_2, false)); map(0x05, MINECRAFT_1_21_2, false),
map(0x06, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
SessionPlayerChatPacket.class, SessionPlayerChatPacket.class,
SessionPlayerChatPacket::new, SessionPlayerChatPacket::new,
map(0x05, MINECRAFT_1_19_3, false), map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false), map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false)); map(0x07, MINECRAFT_1_21_2, false),
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
ClientSettingsPacket.class, ClientSettingsPacket.class,
ClientSettingsPacket::new, ClientSettingsPacket::new,
@@ -303,11 +330,13 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19_4, false), map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false), map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false), map(0x0A, MINECRAFT_1_20_5, false),
map(0x0C, MINECRAFT_1_21_2, false)); map(0x0C, MINECRAFT_1_21_2, false),
map(0x0D, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x11, MINECRAFT_1_20_5, false), map(0x11, MINECRAFT_1_20_5, false),
map(0x13, MINECRAFT_1_21_2, false)); map(0x13, MINECRAFT_1_21_2, false),
map(0x14, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
PluginMessagePacket.class, PluginMessagePacket.class,
PluginMessagePacket::new, PluginMessagePacket::new,
@@ -325,7 +354,8 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_20_2, false), map(0x0F, MINECRAFT_1_20_2, false),
map(0x10, MINECRAFT_1_20_3, false), map(0x10, MINECRAFT_1_20_3, false),
map(0x12, MINECRAFT_1_20_5, false), map(0x12, MINECRAFT_1_20_5, false),
map(0x14, MINECRAFT_1_21_2, false)); map(0x14, MINECRAFT_1_21_2, false),
map(0x15, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
KeepAlivePacket.class, KeepAlivePacket.class,
KeepAlivePacket::new, KeepAlivePacket::new,
@@ -344,7 +374,8 @@ public enum StateRegistry {
map(0x14, MINECRAFT_1_20_2, false), map(0x14, MINECRAFT_1_20_2, false),
map(0x15, MINECRAFT_1_20_3, false), map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false), map(0x18, MINECRAFT_1_20_5, false),
map(0x1A, MINECRAFT_1_21_2, false)); map(0x1A, MINECRAFT_1_21_2, false),
map(0x1B, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
ResourcePackResponsePacket.class, ResourcePackResponsePacket.class,
ResourcePackResponsePacket::new, ResourcePackResponsePacket::new,
@@ -361,12 +392,14 @@ public enum StateRegistry {
map(0x28, MINECRAFT_1_20_3, false), map(0x28, MINECRAFT_1_20_3, false),
map(0x2B, MINECRAFT_1_20_5, false), map(0x2B, MINECRAFT_1_20_5, false),
map(0x2D, MINECRAFT_1_21_2, false), map(0x2D, MINECRAFT_1_21_2, false),
map(0x2F, MINECRAFT_1_21_4, false)); map(0x2F, MINECRAFT_1_21_4, false),
map(0x30, MINECRAFT_1_21_6, false));
serverbound.register( serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false), map(0x0B, MINECRAFT_1_20_2, false),
map(0x0C, MINECRAFT_1_20_5, false), map(0x0C, MINECRAFT_1_20_5, false),
map(0x0E, MINECRAFT_1_21_2, false)); map(0x0E, MINECRAFT_1_21_2, false),
map(0x0F, MINECRAFT_1_21_6, false));
clientbound.register( clientbound.register(
BossBarPacket.class, BossBarPacket.class,
@@ -377,7 +410,8 @@ public enum StateRegistry {
map(0x0D, MINECRAFT_1_17, false), map(0x0D, MINECRAFT_1_17, false),
map(0x0A, MINECRAFT_1_19, false), map(0x0A, MINECRAFT_1_19, false),
map(0x0B, MINECRAFT_1_19_4, false), map(0x0B, MINECRAFT_1_19_4, false),
map(0x0A, MINECRAFT_1_20_2, false)); map(0x0A, MINECRAFT_1_20_2, false),
map(0x09, MINECRAFT_1_21_5, false));
clientbound.register( clientbound.register(
LegacyChatPacket.class, LegacyChatPacket.class,
LegacyChatPacket::new, LegacyChatPacket::new,
@@ -398,7 +432,8 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_19, false), map(0x0E, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_3, false),
map(0x0F, MINECRAFT_1_19_4, false), map(0x0F, MINECRAFT_1_19_4, false),
map(0x10, MINECRAFT_1_20_2, false)); map(0x10, MINECRAFT_1_20_2, false),
map(0x0F, MINECRAFT_1_21_5, false));
clientbound.register( clientbound.register(
AvailableCommandsPacket.class, AvailableCommandsPacket.class,
AvailableCommandsPacket::new, AvailableCommandsPacket::new,
@@ -410,10 +445,32 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_19, false), map(0x0F, MINECRAFT_1_19, false),
map(0x0E, MINECRAFT_1_19_3, false), map(0x0E, MINECRAFT_1_19_3, false),
map(0x10, MINECRAFT_1_19_4, false), map(0x10, MINECRAFT_1_19_4, false),
map(0x11, MINECRAFT_1_20_2, false)); map(0x11, MINECRAFT_1_20_2, false),
map(0x10, MINECRAFT_1_21_5, false));
clientbound.register( clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x16, MINECRAFT_1_20_5, false)); map(0x16, MINECRAFT_1_20_5, false),
map(0x15, MINECRAFT_1_21_5, false));
clientbound.register(
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
map(0x5D, MINECRAFT_1_19_3, true),
map(0x61, MINECRAFT_1_19_4, true),
map(0x63, MINECRAFT_1_20_2, true),
map(0x65, MINECRAFT_1_20_3, true),
map(0x67, MINECRAFT_1_20_5, true),
map(0x6E, MINECRAFT_1_21_2, true),
map(0x6D, MINECRAFT_1_21_5, true),
map(0x72, MINECRAFT_1_21_9, true));
clientbound.register(
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
map(0x5F, MINECRAFT_1_19_3, true),
map(0x63, MINECRAFT_1_19_4, true),
map(0x66, MINECRAFT_1_20_2, true),
map(0x68, MINECRAFT_1_20_3, true),
map(0x6A, MINECRAFT_1_20_5, true),
map(0x71, MINECRAFT_1_21_2, true),
map(0x70, MINECRAFT_1_21_5, true),
map(0x75, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
PluginMessagePacket.class, PluginMessagePacket.class,
PluginMessagePacket::new, PluginMessagePacket::new,
@@ -430,7 +487,8 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_19_3, false), map(0x15, MINECRAFT_1_19_3, false),
map(0x17, MINECRAFT_1_19_4, false), map(0x17, MINECRAFT_1_19_4, false),
map(0x18, MINECRAFT_1_20_2, false), map(0x18, MINECRAFT_1_20_2, false),
map(0x19, MINECRAFT_1_20_5, false)); map(0x19, MINECRAFT_1_20_5, false),
map(0x18, MINECRAFT_1_21_5, false));
clientbound.register( clientbound.register(
DisconnectPacket.class, DisconnectPacket.class,
() -> new DisconnectPacket(this), () -> new DisconnectPacket(this),
@@ -447,7 +505,9 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_19_3, false), map(0x17, MINECRAFT_1_19_3, false),
map(0x1A, MINECRAFT_1_19_4, false), map(0x1A, MINECRAFT_1_19_4, false),
map(0x1B, MINECRAFT_1_20_2, false), map(0x1B, MINECRAFT_1_20_2, false),
map(0x1D, MINECRAFT_1_20_5, false)); map(0x1D, MINECRAFT_1_20_5, false),
map(0x1C, MINECRAFT_1_21_5, false),
map(0x20, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
KeepAlivePacket.class, KeepAlivePacket.class,
KeepAlivePacket::new, KeepAlivePacket::new,
@@ -465,7 +525,9 @@ public enum StateRegistry {
map(0x23, MINECRAFT_1_19_4, false), map(0x23, MINECRAFT_1_19_4, false),
map(0x24, MINECRAFT_1_20_2, false), map(0x24, MINECRAFT_1_20_2, false),
map(0x26, MINECRAFT_1_20_5, false), map(0x26, MINECRAFT_1_20_5, false),
map(0x27, MINECRAFT_1_21_2, false)); map(0x27, MINECRAFT_1_21_2, false),
map(0x26, MINECRAFT_1_21_5, false),
map(0x2B, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
JoinGamePacket.class, JoinGamePacket.class,
JoinGamePacket::new, JoinGamePacket::new,
@@ -483,7 +545,9 @@ public enum StateRegistry {
map(0x28, MINECRAFT_1_19_4, false), map(0x28, MINECRAFT_1_19_4, false),
map(0x29, MINECRAFT_1_20_2, false), map(0x29, MINECRAFT_1_20_2, false),
map(0x2B, MINECRAFT_1_20_5, false), map(0x2B, MINECRAFT_1_20_5, false),
map(0x2C, MINECRAFT_1_21_2, false)); map(0x2C, MINECRAFT_1_21_2, false),
map(0x2B, MINECRAFT_1_21_5, false),
map(0x30, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
RespawnPacket.class, RespawnPacket.class,
RespawnPacket::new, RespawnPacket::new,
@@ -504,13 +568,17 @@ public enum StateRegistry {
map(0x43, MINECRAFT_1_20_2, true), map(0x43, MINECRAFT_1_20_2, true),
map(0x45, MINECRAFT_1_20_3, true), map(0x45, MINECRAFT_1_20_3, true),
map(0x47, MINECRAFT_1_20_5, true), map(0x47, MINECRAFT_1_20_5, true),
map(0x4C, MINECRAFT_1_21_2, true)); map(0x4C, MINECRAFT_1_21_2, true),
map(0x4B, MINECRAFT_1_21_5, true),
map(0x50, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
RemoveResourcePackPacket.class, RemoveResourcePackPacket.class,
RemoveResourcePackPacket::new, RemoveResourcePackPacket::new,
map(0x43, MINECRAFT_1_20_3, false), map(0x43, MINECRAFT_1_20_3, false),
map(0x45, MINECRAFT_1_20_5, false), map(0x45, MINECRAFT_1_20_5, false),
map(0x4A, MINECRAFT_1_21_2, false)); map(0x4A, MINECRAFT_1_21_2, false),
map(0x49, MINECRAFT_1_21_5, false),
map(0x4E, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
ResourcePackRequestPacket.class, ResourcePackRequestPacket.class,
ResourcePackRequestPacket::new, ResourcePackRequestPacket::new,
@@ -531,7 +599,9 @@ public enum StateRegistry {
map(0x42, MINECRAFT_1_20_2, false), map(0x42, MINECRAFT_1_20_2, false),
map(0x44, MINECRAFT_1_20_3, false), map(0x44, MINECRAFT_1_20_3, false),
map(0x46, MINECRAFT_1_20_5, false), map(0x46, MINECRAFT_1_20_5, false),
map(0x4B, MINECRAFT_1_21_2, false)); map(0x4B, MINECRAFT_1_21_2, false),
map(0x4A, MINECRAFT_1_21_5, false),
map(0x4F, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
HeaderAndFooterPacket.class, HeaderAndFooterPacket.class,
HeaderAndFooterPacket::new, HeaderAndFooterPacket::new,
@@ -553,7 +623,9 @@ public enum StateRegistry {
map(0x68, MINECRAFT_1_20_2, true), map(0x68, MINECRAFT_1_20_2, true),
map(0x6A, MINECRAFT_1_20_3, true), map(0x6A, MINECRAFT_1_20_3, true),
map(0x6D, MINECRAFT_1_20_5, true), map(0x6D, MINECRAFT_1_20_5, true),
map(0x74, MINECRAFT_1_21_2, true)); map(0x74, MINECRAFT_1_21_2, true),
map(0x73, MINECRAFT_1_21_5, true),
map(0x78, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
LegacyTitlePacket.class, LegacyTitlePacket.class,
LegacyTitlePacket::new, LegacyTitlePacket::new,
@@ -574,7 +646,9 @@ public enum StateRegistry {
map(0x5F, MINECRAFT_1_20_2, true), map(0x5F, MINECRAFT_1_20_2, true),
map(0x61, MINECRAFT_1_20_3, true), map(0x61, MINECRAFT_1_20_3, true),
map(0x63, MINECRAFT_1_20_5, true), map(0x63, MINECRAFT_1_20_5, true),
map(0x6A, MINECRAFT_1_21_2, true)); map(0x6A, MINECRAFT_1_21_2, true),
map(0x69, MINECRAFT_1_21_5, true),
map(0x6E, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
TitleTextPacket.class, TitleTextPacket.class,
TitleTextPacket::new, TitleTextPacket::new,
@@ -586,7 +660,9 @@ public enum StateRegistry {
map(0x61, MINECRAFT_1_20_2, true), map(0x61, MINECRAFT_1_20_2, true),
map(0x63, MINECRAFT_1_20_3, true), map(0x63, MINECRAFT_1_20_3, true),
map(0x65, MINECRAFT_1_20_5, true), map(0x65, MINECRAFT_1_20_5, true),
map(0x6C, MINECRAFT_1_21_2, true)); map(0x6C, MINECRAFT_1_21_2, true),
map(0x6B, MINECRAFT_1_21_5, true),
map(0x70, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
TitleActionbarPacket.class, TitleActionbarPacket.class,
TitleActionbarPacket::new, TitleActionbarPacket::new,
@@ -598,7 +674,9 @@ public enum StateRegistry {
map(0x48, MINECRAFT_1_20_2, true), map(0x48, MINECRAFT_1_20_2, true),
map(0x4A, MINECRAFT_1_20_3, true), map(0x4A, MINECRAFT_1_20_3, true),
map(0x4C, MINECRAFT_1_20_5, true), map(0x4C, MINECRAFT_1_20_5, true),
map(0x51, MINECRAFT_1_21_2, true)); map(0x51, MINECRAFT_1_21_2, true),
map(0x50, MINECRAFT_1_21_5, true),
map(0x55, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
TitleTimesPacket.class, TitleTimesPacket.class,
TitleTimesPacket::new, TitleTimesPacket::new,
@@ -610,7 +688,9 @@ public enum StateRegistry {
map(0x62, MINECRAFT_1_20_2, true), map(0x62, MINECRAFT_1_20_2, true),
map(0x64, MINECRAFT_1_20_3, true), map(0x64, MINECRAFT_1_20_3, true),
map(0x66, MINECRAFT_1_20_5, true), map(0x66, MINECRAFT_1_20_5, true),
map(0x6D, MINECRAFT_1_21_2, true)); map(0x6D, MINECRAFT_1_21_2, true),
map(0x6C, MINECRAFT_1_21_5, true),
map(0x71, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
TitleClearPacket.class, TitleClearPacket.class,
TitleClearPacket::new, TitleClearPacket::new,
@@ -618,7 +698,8 @@ public enum StateRegistry {
map(0x0D, MINECRAFT_1_19, true), map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true), map(0x0C, MINECRAFT_1_19_3, true),
map(0x0E, MINECRAFT_1_19_4, true), map(0x0E, MINECRAFT_1_19_4, true),
map(0x0F, MINECRAFT_1_20_2, true)); map(0x0F, MINECRAFT_1_20_2, true),
map(0x0E, MINECRAFT_1_21_5, true));
clientbound.register( clientbound.register(
LegacyPlayerListItemPacket.class, LegacyPlayerListItemPacket.class,
LegacyPlayerListItemPacket::new, LegacyPlayerListItemPacket::new,
@@ -638,7 +719,9 @@ public enum StateRegistry {
map(0x39, MINECRAFT_1_19_4, false), map(0x39, MINECRAFT_1_19_4, false),
map(0x3B, MINECRAFT_1_20_2, false), map(0x3B, MINECRAFT_1_20_2, false),
map(0x3D, MINECRAFT_1_20_5, false), map(0x3D, MINECRAFT_1_20_5, false),
map(0x3F, MINECRAFT_1_21_2, false)); map(0x3F, MINECRAFT_1_21_2, false),
map(0x3E, MINECRAFT_1_21_5, false),
map(0x43, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
UpsertPlayerInfoPacket.class, UpsertPlayerInfoPacket.class,
UpsertPlayerInfoPacket::new, UpsertPlayerInfoPacket::new,
@@ -646,11 +729,15 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_19_4, false), map(0x3A, MINECRAFT_1_19_4, false),
map(0x3C, MINECRAFT_1_20_2, false), map(0x3C, MINECRAFT_1_20_2, false),
map(0x3E, MINECRAFT_1_20_5, false), map(0x3E, MINECRAFT_1_20_5, false),
map(0x40, MINECRAFT_1_21_2, false)); map(0x40, MINECRAFT_1_21_2, false),
map(0x3F, MINECRAFT_1_21_5, false),
map(0x44, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x6B, MINECRAFT_1_20_5, false), map(0x6B, MINECRAFT_1_20_5, false),
map(0x72, MINECRAFT_1_21_2, false)); map(0x72, MINECRAFT_1_21_2, false),
map(0x71, MINECRAFT_1_21_5, false),
map(0x76, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
SystemChatPacket.class, SystemChatPacket.class,
SystemChatPacket::new, SystemChatPacket::new,
@@ -661,7 +748,9 @@ public enum StateRegistry {
map(0x67, MINECRAFT_1_20_2, true), map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true), map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true), map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true)); map(0x73, MINECRAFT_1_21_2, true),
map(0x72, MINECRAFT_1_21_5, true),
map(0x77, MINECRAFT_1_21_9, true));
clientbound.register( clientbound.register(
PlayerChatCompletionPacket.class, PlayerChatCompletionPacket.class,
PlayerChatCompletionPacket::new, PlayerChatCompletionPacket::new,
@@ -669,7 +758,8 @@ public enum StateRegistry {
map(0x14, MINECRAFT_1_19_3, true), map(0x14, MINECRAFT_1_19_3, true),
map(0x16, MINECRAFT_1_19_4, true), map(0x16, MINECRAFT_1_19_4, true),
map(0x17, MINECRAFT_1_20_2, true), map(0x17, MINECRAFT_1_20_2, true),
map(0x18, MINECRAFT_1_20_5, true)); map(0x18, MINECRAFT_1_20_5, true),
map(0x17, MINECRAFT_1_21_5, true));
clientbound.register( clientbound.register(
ServerDataPacket.class, ServerDataPacket.class,
ServerDataPacket::new, ServerDataPacket::new,
@@ -680,14 +770,18 @@ public enum StateRegistry {
map(0x47, MINECRAFT_1_20_2, false), map(0x47, MINECRAFT_1_20_2, false),
map(0x49, MINECRAFT_1_20_3, false), map(0x49, MINECRAFT_1_20_3, false),
map(0x4B, MINECRAFT_1_20_5, false), map(0x4B, MINECRAFT_1_20_5, false),
map(0x50, MINECRAFT_1_21_2, false)); map(0x50, MINECRAFT_1_21_2, false),
map(0x4F, MINECRAFT_1_21_5, false),
map(0x54, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
StartUpdatePacket.class, StartUpdatePacket.class,
() -> StartUpdatePacket.INSTANCE, () -> StartUpdatePacket.INSTANCE,
map(0x65, MINECRAFT_1_20_2, false), map(0x65, MINECRAFT_1_20_2, false),
map(0x67, MINECRAFT_1_20_3, false), map(0x67, MINECRAFT_1_20_3, false),
map(0x69, MINECRAFT_1_20_5, false), map(0x69, MINECRAFT_1_20_5, false),
map(0x70, MINECRAFT_1_21_2, false)); map(0x70, MINECRAFT_1_21_2, false),
map(0x6F, MINECRAFT_1_21_5, false),
map(0x74, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
BundleDelimiterPacket.class, BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE, () -> BundleDelimiterPacket.INSTANCE,
@@ -696,17 +790,20 @@ public enum StateRegistry {
TransferPacket.class, TransferPacket.class,
TransferPacket::new, TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, false), map(0x73, MINECRAFT_1_20_5, false),
map(0x7A, MINECRAFT_1_21_2, false)); map(0x7A, MINECRAFT_1_21_2, false),
map(0x7F, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket.class,
ClientboundCustomReportDetailsPacket::new, ClientboundCustomReportDetailsPacket::new,
map(0x7A, MINECRAFT_1_21, false), map(0x7A, MINECRAFT_1_21, false),
map(0x81, MINECRAFT_1_21_2, false)); map(0x81, MINECRAFT_1_21_2, false),
map(0x86, MINECRAFT_1_21_9, false));
clientbound.register( clientbound.register(
ClientboundServerLinksPacket.class, ClientboundServerLinksPacket.class,
ClientboundServerLinksPacket::new, ClientboundServerLinksPacket::new,
map(0x7B, MINECRAFT_1_21, false), map(0x7B, MINECRAFT_1_21, false),
map(0x82, MINECRAFT_1_21_2, false)); map(0x82, MINECRAFT_1_21_2, false),
map(0x87, MINECRAFT_1_21_9, false));
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new, clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
map(0x41, ProtocolVersion.MINECRAFT_1_9, true), map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
map(0x43, ProtocolVersion.MINECRAFT_1_12, true), map(0x43, ProtocolVersion.MINECRAFT_1_12, true),

View File

@@ -39,6 +39,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int UNCOMPRESSED_CAP = private static final int UNCOMPRESSED_CAP =
Boolean.getBoolean("velocity.increased-compression-cap") Boolean.getBoolean("velocity.increased-compression-cap")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE; ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
private static final boolean SKIP_COMPRESSION_VALIDATION = Boolean.getBoolean("velocity.skip-uncompressed-packet-size-validation");
private int threshold; private int threshold;
private final VelocityCompressor compressor; private final VelocityCompressor compressor;
@@ -52,6 +53,11 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int claimedUncompressedSize = ProtocolUtils.readVarInt(in); int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
if (claimedUncompressedSize == 0) { if (claimedUncompressedSize == 0) {
if (!SKIP_COMPRESSION_VALIDATION) {
int actualUncompressedSize = in.readableBytes();
checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than"
+ " threshold %s", actualUncompressedSize, threshold);
}
// This message is not compressed. // This message is not compressed.
out.add(in.retain()); out.add(in.retain());
return; return;

View File

@@ -46,7 +46,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
if (uncompressed < threshold) { if (uncompressed < threshold) {
// Under the threshold, there is nothing to do. // Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, uncompressed + 1); ProtocolUtils.writeVarInt(out, uncompressed + 1);
ProtocolUtils.writeVarInt(out, 0); out.writeByte(0);
out.writeBytes(msg); out.writeBytes(msg);
} else { } else {
handleCompressed(ctx, msg, out); handleCompressed(ctx, msg, out);
@@ -57,7 +57,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throws DataFormatException { throws DataFormatException {
int uncompressed = msg.readableBytes(); int uncompressed = msg.readableBytes();
ProtocolUtils.write21BitVarInt(out, 0); // Dummy packet length out.writeMedium(0); // Reserve the packet length
ProtocolUtils.writeVarInt(out, uncompressed); ProtocolUtils.writeVarInt(out, uncompressed);
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
@@ -72,11 +72,8 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet."); throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet.");
} }
int writerIndex = out.writerIndex();
int packetLength = out.readableBytes() - 3; int packetLength = out.readableBytes() - 3;
out.writerIndex(0); out.setMedium(0, ProtocolUtils.encode21BitVarInt(packetLength)); // Rewrite packet length
ProtocolUtils.write21BitVarInt(out, packetLength); // Rewrite packet length
out.writerIndex(writerIndex);
} }
@Override @Override

View File

@@ -96,8 +96,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
} }
private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception { private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception {
int expectedMinLen = packet.expectedMinLength(buf, direction, registry.version); int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(buf, direction, registry.version); int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version);
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) { if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes()); throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
} }
@@ -135,7 +135,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
private String getExtraConnectionDetail(int packetId) { private String getExtraConnectionDetail(int packetId) {
return "Direction " + direction + " Protocol " + registry.version + " State " + state return "Direction " + direction + " Protocol " + registry.version + " State " + state
+ " ID " + Integer.toHexString(packetId); + " ID 0x" + Integer.toHexString(packetId);
} }
public void setProtocolVersion(ProtocolVersion protocolVersion) { public void setProtocolVersion(ProtocolVersion protocolVersion) {

View File

@@ -54,6 +54,19 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
msg.encode(out, direction, registry.version); msg.encode(out, direction, registry.version);
} }
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, MinecraftPacket msg,
boolean preferDirect) throws Exception {
int hint = msg.encodeSizeHint(direction, registry.version);
if (hint < 0) {
return super.allocateBuffer(ctx, msg, preferDirect);
}
int packetId = this.registry.getPacketId(msg);
int totalHint = ProtocolUtils.varIntBytes(packetId) + hint;
return preferDirect ? ctx.alloc().ioBuffer(totalHint) : ctx.alloc().heapBuffer(totalHint);
}
public void setProtocolVersion(final ProtocolVersion protocolVersion) { public void setProtocolVersion(final ProtocolVersion protocolVersion) {
this.registry = state.getProtocolRegistry(direction, protocolVersion); this.registry = state.getProtocolRegistry(direction, protocolVersion);
} }

View File

@@ -19,21 +19,51 @@ package com.velocitypowered.proxy.protocol.netty;
import static io.netty.util.ByteProcessor.FIND_NON_NUL; import static io.netty.util.ByteProcessor.FIND_NON_NUL;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding. * Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding.
*/ */
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
private static final Logger LOGGER = LogManager.getLogger(MinecraftVarintFrameDecoder.class);
private static final QuietRuntimeException FRAME_DECODER_FAILED =
new QuietRuntimeException("A packet frame decoder failed. For more information, launch "
+ "Velocity with -Dvelocity.packet-decode-logging=true to see more.");
private static final QuietDecoderException BAD_PACKET_LENGTH = private static final QuietDecoderException BAD_PACKET_LENGTH =
new QuietDecoderException("Bad packet length"); new QuietDecoderException("Bad packet length");
private static final QuietDecoderException VARINT_TOO_BIG = private static final QuietDecoderException VARINT_TOO_BIG =
new QuietDecoderException("VarInt too big"); new QuietDecoderException("VarInt too big");
private static final QuietDecoderException UNKNOWN_PACKET =
new QuietDecoderException("Unknown packet");
private final ProtocolUtils.Direction direction;
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private StateRegistry state;
/**
* Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}.
*
* @param direction the direction from which we decode from
*/
public MinecraftVarintFrameDecoder(ProtocolUtils.Direction direction) {
this.direction = direction;
this.registry = StateRegistry.HANDSHAKE.getProtocolRegistry(
direction, ProtocolVersion.MINIMUM_VERSION);
this.state = StateRegistry.HANDSHAKE;
}
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
@@ -53,15 +83,22 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
// try to read the length of the packet // try to read the length of the packet
in.markReaderIndex(); in.markReaderIndex();
int preIndex = in.readerIndex();
int length = readRawVarInt21(in); int length = readRawVarInt21(in);
if (preIndex == in.readerIndex()) { if (packetStart == in.readerIndex()) {
return; return;
} }
if (length < 0) { if (length < 0) {
throw BAD_PACKET_LENGTH; throw BAD_PACKET_LENGTH;
} }
if (length > 0) {
if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) {
if (validateServerboundHandshakePacket(in, length)) {
return;
}
}
}
// note that zero-length packets are ignored // note that zero-length packets are ignored
if (length > 0) { if (length > 0) {
if (in.readableBytes() < length) { if (in.readableBytes() < length) {
@@ -72,6 +109,51 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
} }
} }
private boolean validateServerboundHandshakePacket(ByteBuf in, int length) throws Exception {
StateRegistry.PacketRegistry.ProtocolRegistry registry =
state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION);
final int index = in.readerIndex();
final int packetId = readRawVarInt21(in);
// Index hasn't changed, we've read nothing
if (index == in.readerIndex()) {
in.resetReaderIndex();
return true;
}
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
MinecraftPacket packet = registry.createPacket(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, in.readableBytes());
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
}
in.readerIndex(index);
return false;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (MinecraftDecoder.DEBUG) {
LOGGER.atWarn()
.withThrowable(cause)
.log("Exception caught while decoding frame for {}", ctx.channel().remoteAddress());
}
super.exceptionCaught(ctx, cause);
}
/** /**
* Reads a VarInt from the buffer of up to 21 bits in size. * Reads a VarInt from the buffer of up to 21 bits in size.
* *
@@ -141,4 +223,26 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
} }
return result | (tmp & 0x7F) << 14; return result | (tmp & 0x7F) << 14;
} }
private Exception handleOverflow(MinecraftPacket packet, int expected, int actual) {
if (MinecraftDecoder.DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
+ "big (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return FRAME_DECODER_FAILED;
}
}
private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) {
if (MinecraftDecoder.DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
+ "small (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return FRAME_DECODER_FAILED;
}
}
public void setState(StateRegistry stateRegistry) {
this.state = stateRegistry;
}
} }

View File

@@ -50,12 +50,14 @@ import java.util.Deque;
import java.util.Iterator; import java.util.Iterator;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class AvailableCommandsPacket implements MinecraftPacket { public class AvailableCommandsPacket implements MinecraftPacket {
private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0; private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0;
private static final Predicate<CommandSource> PLACEHOLDER_REQUIREMENT = source -> true;
private static final byte NODE_TYPE_ROOT = 0x00; private static final byte NODE_TYPE_ROOT = 0x00;
private static final byte NODE_TYPE_LITERAL = 0x01; private static final byte NODE_TYPE_LITERAL = 0x01;
@@ -65,6 +67,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
private static final byte FLAG_EXECUTABLE = 0x04; private static final byte FLAG_EXECUTABLE = 0x04;
private static final byte FLAG_IS_REDIRECT = 0x08; private static final byte FLAG_IS_REDIRECT = 0x08;
private static final byte FLAG_HAS_SUGGESTIONS = 0x10; private static final byte FLAG_HAS_SUGGESTIONS = 0x10;
private static final byte FLAG_IS_RESTRICTED = 0x20;
private @MonotonicNonNull RootCommandNode<CommandSource> rootNode; private @MonotonicNonNull RootCommandNode<CommandSource> rootNode;
@@ -146,6 +149,9 @@ public class AvailableCommandsPacket implements MinecraftPacket {
if (node.getCommand() != null) { if (node.getCommand() != null) {
flags |= FLAG_EXECUTABLE; flags |= FLAG_EXECUTABLE;
} }
if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) {
flags |= FLAG_IS_RESTRICTED;
}
if (node instanceof LiteralCommandNode<?>) { if (node instanceof LiteralCommandNode<?>) {
flags |= NODE_TYPE_LITERAL; flags |= NODE_TYPE_LITERAL;
@@ -289,6 +295,11 @@ public class AvailableCommandsPacket implements MinecraftPacket {
args.executes(PLACEHOLDER_COMMAND); args.executes(PLACEHOLDER_COMMAND);
} }
// If restricted, add empty requirement
if ((flags & FLAG_IS_RESTRICTED) != 0) {
args.requires(PLACEHOLDER_REQUIREMENT);
}
this.built = args.build(); this.built = args.build();
} }
} }
@@ -351,4 +362,12 @@ public class AvailableCommandsPacket implements MinecraftPacket {
return builder.buildFuture(); return builder.buildFuture();
} }
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of
// 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized
// amount.
return 128 * 1024;
}
} }

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.sound.Sound;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
public class ClientboundSoundEntityPacket implements MinecraftPacket {
private static final Random SEEDS_RANDOM = new Random();
private Sound sound;
private @Nullable Float fixedRange;
private int emitterEntityId;
public ClientboundSoundEntityPacket() {}
public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) {
this.sound = sound;
this.fixedRange = fixedRange;
this.emitterEntityId = emitterEntityId;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID
ProtocolUtils.writeMinimalKey(buf, sound.name());
buf.writeBoolean(fixedRange != null);
if (fixedRange != null)
buf.writeFloat(fixedRange);
ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source());
ProtocolUtils.writeVarInt(buf, emitterEntityId);
buf.writeFloat(sound.volume());
buf.writeFloat(sound.pitch());
buf.writeLong(sound.seed().orElse(SEEDS_RANDOM.nextLong()));
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public Sound getSound() {
return sound;
}
public void setSound(Sound sound) {
this.sound = sound;
}
public @Nullable Float getFixedRange() {
return fixedRange;
}
public void setFixedRange(@Nullable Float fixedRange) {
this.fixedRange = fixedRange;
}
public int getEmitterEntityId() {
return emitterEntityId;
}
public void setEmitterEntityId(int emitterEntityId) {
this.emitterEntityId = emitterEntityId;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import javax.annotation.Nullable;
public class ClientboundStopSoundPacket implements MinecraftPacket {
private @Nullable Sound.Source source;
private @Nullable Key soundName;
public ClientboundStopSoundPacket() {}
public ClientboundStopSoundPacket(SoundStop soundStop) {
this(soundStop.source(), soundStop.sound());
}
public ClientboundStopSoundPacket(@Nullable Sound.Source source, @Nullable Key soundName) {
this.source = source;
this.soundName = soundName;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = buf.readByte();
if ((flagsBitmask & 1) != 0) {
source = ProtocolUtils.readSoundSource(buf, protocolVersion);
} else {
source = null;
}
if ((flagsBitmask & 2) != 0) {
soundName = ProtocolUtils.readKey(buf);
} else {
soundName = null;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = 0;
if (source != null && soundName == null) {
flagsBitmask |= 1;
} else if (soundName != null && source == null) {
flagsBitmask |= 2;
} else if (source != null /*&& sound != null*/) {
flagsBitmask |= 3;
}
buf.writeByte(flagsBitmask);
if (source != null) {
ProtocolUtils.writeSoundSource(buf, protocolVersion, source);
}
if (soundName != null) {
ProtocolUtils.writeMinimalKey(buf, soundName);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Nullable
public Sound.Source getSource() {
return source;
}
public void setSource(@Nullable Sound.Source source) {
this.source = source;
}
@Nullable
public Key getSoundName() {
return soundName;
}
public void setSoundName(@Nullable Key soundName) {
this.soundName = soundName;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class DialogClearPacket implements MinecraftPacket {
public static final DialogClearPacket INSTANCE = new DialogClearPacket();
private DialogClearPacket() {
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
public class DialogShowPacket implements MinecraftPacket {
private final StateRegistry state;
private int id;
private BinaryTag nbt;
public DialogShowPacket(final StateRegistry state) {
this.state = state;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.id = this.state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf);
if (this.id == 0) {
this.nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader());
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (this.state == StateRegistry.CONFIG) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
} else {
ProtocolUtils.writeVarInt(buf, this.id);
if (this.id == 0) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

View File

@@ -107,7 +107,7 @@ public class EncryptionResponsePacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// It turns out these come out to the same length, whether we're talking >=1.8 or not. // It turns out these come out to the same length, whether we're talking >=1.8 or not.
// The length prefix always winds up being 2 bytes. // The length prefix always winds up being 2 bytes.
int base = 256 + 2 + 2; int base = 256 + 2 + 2;
@@ -123,8 +123,8 @@ public class EncryptionResponsePacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
int base = expectedMaxLength(buf, direction, version); int base = decodeExpectedMaxLength(buf, direction, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// These are "optional" // These are "optional"
base -= 128 + 8; base -= 128 + 8;

View File

@@ -24,6 +24,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public class HandshakePacket implements MinecraftPacket { public class HandshakePacket implements MinecraftPacket {
@@ -106,4 +107,23 @@ public class HandshakePacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 7;
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
}
} }

View File

@@ -36,7 +36,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) { ProtocolVersion version) {
return 0; return 0;
} }

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@@ -86,4 +87,9 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
} }

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@@ -88,4 +89,9 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
} }

View File

@@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -143,4 +144,9 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
public PluginMessagePacket touch(Object hint) { public PluginMessagePacket touch(Object hint) {
return (PluginMessagePacket) super.touch(hint); return (PluginMessagePacket) super.touch(hint);
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
} }

View File

@@ -22,6 +22,7 @@ import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -121,4 +122,9 @@ public class ServerDataPacket implements MinecraftPacket {
public void setSecureChatEnforced(boolean secureChatEnforced) { public void setSecureChatEnforced(boolean secureChatEnforced) {
this.secureChatEnforced = secureChatEnforced; this.secureChatEnforced = secureChatEnforced;
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return 8 * 1024;
}
} }

View File

@@ -150,7 +150,7 @@ public class ServerLoginPacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
// legal on the protocol level. // legal on the protocol level.
int base = 1 + (16 * 3); int base = 1 + (16 * 3);

View File

@@ -23,6 +23,7 @@ import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.VelocityProperties; import com.velocitypowered.proxy.util.VelocityProperties;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.List; import java.util.List;
@@ -132,4 +133,11 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
}
} }

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket {
public ServerboundCustomClickActionPacket() {
super(null);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
}

View File

@@ -44,12 +44,12 @@ public class StatusPingPacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 8; return 8;
} }
@Override @Override
public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 8; return 8;
} }
} }

View File

@@ -53,7 +53,7 @@ public class StatusRequestPacket implements MinecraftPacket {
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 0; return 0;
} }
} }

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -66,4 +67,9 @@ public class StatusResponsePacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return ProtocolUtils.stringSizeHint(this.status);
}
} }

View File

@@ -36,6 +36,8 @@ import org.jetbrains.annotations.Nullable;
public class UpsertPlayerInfoPacket implements MinecraftPacket { public class UpsertPlayerInfoPacket implements MinecraftPacket {
private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants();
private final EnumSet<Action> actions; private final EnumSet<Action> actions;
private final List<Entry> entries; private final List<Entry> entries;
@@ -85,14 +87,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) { ProtocolVersion protocolVersion) {
Action[] actions = Action.class.getEnumConstants(); byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)];
byte[] bytes = new byte[-Math.floorDiv(-actions.length, 8)];
buf.readBytes(bytes); buf.readBytes(bytes);
BitSet actionSet = BitSet.valueOf(bytes); BitSet actionSet = BitSet.valueOf(bytes);
for (int idx = 0; idx < actions.length; idx++) { for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
if (actionSet.get(idx)) { if (actionSet.get(idx)) {
addAction(actions[idx]); addAction(ALL_ACTIONS[idx]);
} }
} }
@@ -109,14 +110,13 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
@Override @Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) { ProtocolVersion protocolVersion) {
Action[] actions = Action.class.getEnumConstants(); BitSet set = new BitSet(ALL_ACTIONS.length);
BitSet set = new BitSet(actions.length); for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
for (int idx = 0; idx < actions.length; idx++) { set.set(idx, this.actions.contains(ALL_ACTIONS[idx]));
set.set(idx, this.actions.contains(actions[idx]));
} }
byte[] bytes = set.toByteArray(); byte[] bytes = set.toByteArray();
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-actions.length, 8))); buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8)));
ProtocolUtils.writeVarInt(buf, this.entries.size()); ProtocolUtils.writeVarInt(buf, this.entries.size());
for (Entry entry : this.entries) { for (Entry entry : this.entries) {
@@ -133,12 +133,6 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
return handler.handle(this); return handler.handle(this);
} }
public BitSet readFixedBitSet(ByteBuf buf, int param0) {
byte[] var0 = new byte[-Math.floorDiv(-param0, 8)];
buf.readBytes(var0);
return BitSet.valueOf(var0);
}
public enum Action { public enum Action {
ADD_PLAYER((ignored, buf, info) -> { // read ADD_PLAYER((ignored, buf, info) -> { // read
info.profile = new GameProfile( info.profile = new GameProfile(

View File

@@ -58,6 +58,13 @@ public class ArgumentIdentifier {
this.versionById = ImmutableMap.copyOf(temp); this.versionById = ImmutableMap.copyOf(temp);
} }
@Override
public String toString() {
return "ArgumentIdentifier{" +
"identifier='" + identifier + '\'' +
'}';
}
public String getIdentifier() { public String getIdentifier() {
return identifier; return identifier;
} }

View File

@@ -22,6 +22,8 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
@@ -43,6 +45,8 @@ import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -85,9 +89,6 @@ public class ArgumentPropertyRegistry {
ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion); ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion);
ArgumentPropertySerializer<?> serializer = byIdentifier.get(identifier); ArgumentPropertySerializer<?> serializer = byIdentifier.get(identifier);
if (serializer == null) {
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
}
Object result = serializer.deserialize(buf, protocolVersion); Object result = serializer.deserialize(buf, protocolVersion);
if (result instanceof ArgumentType) { if (result instanceof ArgumentType) {
@@ -154,7 +155,7 @@ public class ArgumentPropertyRegistry {
* @param protocolVersion the protocol version to use * @param protocolVersion the protocol version to use
* @return the identifier read from the buffer * @return the identifier read from the buffer
*/ */
public static ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) { public static @NotNull ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(MINECRAFT_1_19)) { if (protocolVersion.noLessThan(MINECRAFT_1_19)) {
int id = ProtocolUtils.readVarInt(buf); int id = ProtocolUtils.readVarInt(buf);
for (ArgumentIdentifier i : byIdentifier.keySet()) { for (ArgumentIdentifier i : byIdentifier.keySet()) {
@@ -163,6 +164,7 @@ public class ArgumentPropertyRegistry {
return i; return i;
} }
} }
throw new IllegalArgumentException("Argument type identifier " + id + " unknown.");
} else { } else {
String identifier = ProtocolUtils.readString(buf); String identifier = ProtocolUtils.readString(buf);
for (ArgumentIdentifier i : byIdentifier.keySet()) { for (ArgumentIdentifier i : byIdentifier.keySet()) {
@@ -170,8 +172,8 @@ public class ArgumentPropertyRegistry {
return i; return i;
} }
} }
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
} }
return null;
} }
static { static {
@@ -206,65 +208,79 @@ public class ArgumentPropertyRegistry {
empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14))); empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14)));
empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15))); empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15)));
empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16))); empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16)));
empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17))); empty(id("minecraft:component", mapSet(MINECRAFT_1_21_6, 18), mapSet(MINECRAFT_1_19, 17)));
empty(id("minecraft:style", mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 empty(id("minecraft:style", mapSet(MINECRAFT_1_21_6, 19), mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3
empty(id("minecraft:message", mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); empty(id("minecraft:message", mapSet(MINECRAFT_1_21_6, 20), mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18)));
empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14 empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_21_6, 21), mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14
empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14 empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_21_6, 22), mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14
empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21))); empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_21_6, 23), mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21)));
empty(id("minecraft:objective", mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22))); empty(id("minecraft:objective", mapSet(MINECRAFT_1_21_6, 24), mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22)));
empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23))); empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_21_6, 25), mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23)));
empty(id("minecraft:operation", mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24))); empty(id("minecraft:operation", mapSet(MINECRAFT_1_21_6, 26), mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24)));
empty(id("minecraft:particle", mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25))); empty(id("minecraft:particle", mapSet(MINECRAFT_1_21_6, 27), mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25)));
empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 empty(id("minecraft:angle", mapSet(MINECRAFT_1_21_6, 28), mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2
empty(id("minecraft:rotation", mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27))); empty(id("minecraft:rotation", mapSet(MINECRAFT_1_21_6, 29), mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27)));
empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_21_6, 30), mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28)));
empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE); empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_21_6, 31), mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)),
empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30))); ByteArgumentPropertySerializer.BYTE);
empty(id("minecraft:team", mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31))); empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_21_6, 32), mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30)));
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32))); empty(id("minecraft:team", mapSet(MINECRAFT_1_21_6, 33), mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31)));
empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5 empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_21_6, 34), mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32)));
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34), mapSet(MINECRAFT_1_19, 33))); empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_21_6, 35), mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_21_6, 36), mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34),
mapSet(MINECRAFT_1_19, 33)));
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34))); empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34)));
empty(id("minecraft:function", mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34), empty(id("minecraft:function", mapSet(MINECRAFT_1_21_6, 37), mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35),
mapSet(MINECRAFT_1_19, 35))); mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35)));
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35), empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_21_6, 38), mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36),
mapSet(MINECRAFT_1_19, 36))); mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36)));
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36), empty(id("minecraft:int_range", mapSet(MINECRAFT_1_21_6, 39), mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37),
mapSet(MINECRAFT_1_19, 37))); mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37)));
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37), empty(id("minecraft:float_range", mapSet(MINECRAFT_1_21_6, 40), mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38),
mapSet(MINECRAFT_1_19, 38))); mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38)));
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39))); empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39)));
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40))); empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40)));
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38), empty(id("minecraft:dimension", mapSet(MINECRAFT_1_21_6, 41), mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39),
mapSet(MINECRAFT_1_19, 41))); mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41)));
empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40), mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_21_6, 42), mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40),
mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3
empty(id("minecraft:time", mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40), empty(id("minecraft:time", mapSet(MINECRAFT_1_21_6, 43), mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41),
mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14 mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41), register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_21_6, 44), mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42),
mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43), mapSet(MINECRAFT_1_19_3, 42)), register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_21_6, 45), mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43),
mapSet(MINECRAFT_1_19_3, 42)),
RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.class,
RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY); RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY);
register(id("minecraft:resource", mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43), register(id("minecraft:resource", mapSet(MINECRAFT_1_21_6, 46), mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44),
mapSet(MINECRAFT_1_19, 44)), mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), register(id("minecraft:resource_key", mapSet(MINECRAFT_1_21_6, 47), mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45),
mapSet(MINECRAFT_1_19_3, 44)),
RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.class,
RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY); RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY);
register(id("minecraft:resource_selector", mapSet(MINECRAFT_1_21_6, 48), mapSet(MINECRAFT_1_21_5, 47)),
RegistryKeyArgumentList.ResourceSelector.class,
RegistryKeyArgumentList.ResourceSelector.Serializer.REGISTRY);
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19 empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_6, 49), mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47),
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19 mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_21_6, 50), mapSet(MINECRAFT_1_21_5, 49), mapSet(MINECRAFT_1_20_5, 48),
mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
mapSet(MINECRAFT_1_19, 47))); // added in 1.16 mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_20_5, 50))); empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_20_5, 51))); empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_20_5, 52))); empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6
empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
// Crossstitch support // Crossstitch support
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);

Some files were not shown because too many files have changed in this diff Show More