From 4aa9ee77351531430019b4725427c5d9e5bed913 Mon Sep 17 00:00:00 2001 From: Ivan <35296264+Kambet@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:48:08 +0300 Subject: [PATCH 01/34] fix: NoClassDefFoundError on FastUtil's toIntArray (#1475) --- proxy/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index 9b74dae5..ab736e20 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -51,7 +51,11 @@ tasks { exclude("it/unimi/dsi/fastutil/ints/*Int2Short*") exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*") 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/Int*Pair") exclude("it/unimi/dsi/fastutil/ints/IntLinked*") From d77e508e9cb65894848f4dfbcf3f7c9341d64225 Mon Sep 17 00:00:00 2001 From: David <54660361+NonSwag@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:04:56 +0100 Subject: [PATCH 02/34] [ci skip] Fix typo in TabListEntry latency docs (#1479) --- .../java/com/velocitypowered/api/proxy/player/TabListEntry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index b5140776..350dc896 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -80,7 +80,7 @@ public interface TabListEntry extends KeyIdentifiable { *
  • 150-300 will display 4 bars
  • *
  • 300-600 will display 3 bars
  • *
  • 600-1000 will display 2 bars
  • - *
  • A latency move than 1 second will display 1 bar
  • + *
  • A latency greater than 1 second will display 1 bar
  • * * * @return latency set for {@code this} entry From 39191957eadab3bf3088033c90a47fbf6ccd8055 Mon Sep 17 00:00:00 2001 From: Kichura <68134602+Kichura@users.noreply.github.com> Date: Sat, 21 Dec 2024 09:02:30 +0100 Subject: [PATCH 03/34] Migrate to setup-gradle, Gradle 8.11.1. (#1480) --- .github/workflows/gradle.yml | 7 ++++--- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 11 +++++++---- gradlew.bat | 6 ++++-- settings.gradle.kts | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index acec1f85..c0ad9c30 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -10,13 +10,14 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v3 + with: + persist-credentials: false + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: 17 distribution: 'temurin' - cache: 'gradle' - name: Build with Gradle run: ./gradlew build diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ zF^}Ea+#Je`Xy6;#D0FPnSrR%Y!QGA~NA^{oWmW8C<3dr{x6wWQ{4+bzemqV5W$i5~ z=J0jXZ>uZb>DT@0Ks?4QJ{`z?8JWl3$y;2pj#$XP*pv$>$g(z43{YH9KmmR6<#sIn zA`#=0#sgycaBQ^&}Xba!|KaZ8~b30v~nLt z9%#gz_*=~KD{3t^X~l>480*}PhKN=??g`RV|4Ud{Gyyl187MJ}r(#e+H$GEdI+p1s zq_25h;fV)$EPK%Dw-(G=f`yHB-_tttsC!?k7*#!|4a>`Ahj8nm?&n>NRs%jkZW^3-0P_yMP5&*6a26{MRj1&TPF zyE#|c)5uUHzMWx=rMKpuPih*V=S;W3MzIZTw2uTbr}8`p2bm+Z6Sa%vvWAWSf4H)p(+ zSQ8;EvUa#wqWV+9vmIio(%7wukK2SwjUS8Yl%Rq%=~PU)2$Tvm6`1!r3H@U#_|bB0 zmlT1PS3wPB(b&^+@YY7Y$n4l3mV3-X0$>z|gZp6O*Lhzn&?Gad2ZCF;+#95-Y?#y+ z?*l@Yf=a4w{Px=o!N|3~_XKfk&G;fN>Ps&dp2FpA~qD=0~=!NOS@B#XAKKkND>Y{4>rqxrViKD7;?>j8`R` z&G)3FN|dfsxnaI^!d1G%=>AbTTxZWo;n-DLrQ!sj=f~VAOe5zhGS(dgx|!ls62fbX zV@<7Ck^!}R=`Swr?(7w1rY6Nmq~sfXJ?TiKJLn=&SQdEt9$@0 zA+h1Wbwbri0s-stc8yVq;mRa6@kEf8^KXUz&jcic!+avDvvJFa>k0ioWug=T3oPw; zyj4it&0@>_*uI@2=^+T7sL1_!^aJW@Xfo8aC#3^WtQC7fET8b9C} z*u^ue6Ojn z7@(eskJ2+cNnH9~VyfIh<-|7!je~vGy*odz(sk-u$~SrYF3glruZ*W`{sqnS+9=;Z zh{D@MSG91%lr&ua8%$sJF%y1I<|e;EdfJykY8#D$Hc_81n5`$7;1N|b0tvvPLzSg& zn7!5x?T*@rQUKcUhTIjV(rw*5oQYlm5DbEO?60#mohHfbR$3_x#+PZoYi@Vd4`#YgKyTd^!4n{fN~WZDY61sAOm6 zl!d^i*a01QxpWM9Pcl?&{RgO}uq%ErOk5WpECvnfEh!*YP&1Sl)uTN4hg??Vqs~i5 zYsfufz3?{TtwuBN=`0~Qg1PlWH#OGG$ zLLWU17$v``)CE1cds_7kj8mJ{-+l8{DS|zAQ&3|qpOY=!J|kXUhXue9|H>4gqk|n) z-i34GmxLFj8asb3D#D&=ya*a5`C<=o?G;Ev^LV%;l#nH#O=7Nh@z1Do>j6Q;I5S2P zhg|AZbC&|c7}uSJt57s2IK#rSWuararn-02dkptTjo*R{c5o(bWV}_k3BBnKcE|6l zrHl&ezUyw^DmaMdDFVn<8ZY=7_{u{uW&*F<7Al6};lD(u;SB=RpIwI)PTyL=e25h* zGi{lRT}snjbMK~IUx|EGonH+w;iC2Ws)x>=5_{5$m?K z5(*1jMn%u0V1Y%m@`YS3kskt~`1p(rA4uk;Cs!w^KL$w>MH)+cP6|XKr4FfHIATJH z!EGAK4N>1yFR`-zW|w%ByRe#=&kA&#WyUldDGpt!wf-8SFWiSi!5QZL+l7*CE?u!NW1T$<1rdLJ9y3u{_zvHaM?#Rm4 zFk}^1!ffcrB|XK3gsO-s=wr*sUe&^$yN|KxrA)uW00Gu60%pw_+DcUjW`oW<35OC8 zq2{j8SgC}W$?10pvFU83(SL$%C?Kctu3*cs0aa%q!fjn1%xD*Jrm!F3HGR9-C{b?- zHp(cL;ezXMpL@0-1v0DMWddSDNZ5h?q50cOZyVi#bU3&PWE=(hpVn|M4_KYG5h9LffKNRsfhr^=SYiKg?#r&HNMi2@cd4aYL9lw(5_IvQJ zcB*DD()hUSAD^PdA0y|QrVnqwgI@pUXZXjHq3lG2OU&7sPOxxU$Y3&ytj6Qb=2#cC z;{d-{k|xI*bu+Vy&N+}{i(+1me!M;nshY_*&ZQLTGG*xNw#{RpI`3^eGfHck+*38NRgiGahkFethtVY=czJs#)VVc{T65rhU#3Vf?X)8f0)X{w!J3J{z|Sq|%?)nA+zo?$>L9@o`Kc|*7sJo4UjIqu0Ir~S5k^vEH};6K?-dZ0h*m%-1L zf!VC%YbM1~sZOG5zu&Sh>R;(md*_)kGHP)<;OA44W?y53PI%{&@MEN}9TOiqu+1a3AGetBr$c)Ao3OX>iGxmA;^^_alwS818r4Pn&uYe^;z6dh z)68T|AN=hjNdGpF7n>y+RTAZc9&opTXf zqWfK_dUv=mW{p_vN>|(cIkd(+Jy}qnK{IW%X*3!l`^H~FbAHwof+vLZ0C2ZXN1$v7 zgN&R9c8IO`fkR{6U%ERq8FN<1DQYbAN0-pH7EfcA{A&nhT!Be>jj>J!bNRw4NF|}! z1c70_#fkk!VQ!q1h2ff@`yDyrI1`np>*e#D4-Z~*!T^8#o*$V~!8bWQaie?P@KGBb z8rXc!YDL!$3ZgZZ%;-%~0Kn<+d+{xJ$stQbtN8GWV?MCJvzPU|(E(1z;rFw{&6vy) z3*@y%7Tx8rH-p$boS>bLyod?OKRE8v`QSBvGfY6f}_{Zo1q85xoyOF16n~yHx2W ziydUoYLkJmzq|n&2S(O!ZmLdP1(o1Jsq88cX)x3V-BK5eF&0e_0G!5?U7&3KN0`mc zH&Lt)q8!d_VgzxyL^(@xrbp2y)Hmr^V48));RSfE=*Ly0uh9!$3dv-vMZr2URf@l5zdwLjGZB zugY>7_fd_vbV*Qv1?H~>Z%RD%nEeFSI$n$$Lrpc6g>i4+XdBB!%zM$Bhrz5Swzyg? z$~I~n@~-wTBY3-T&pr+|gC+OHDoR?I(eLWa{Z#Rsh>lc~%u0!&R|s0pA*w<7QZ}{i z*AFr~0F3y~f$MGh_HDL7J_1?SxKL}fWIk!$G}`^{)xh*dZ5kK>xGL9>V`WZZg_ z)^Vm)EQK`yfh5KiR(vb&aHvhich z_5o+{d~0+4BEBqYJXyXBIEb1UgVDs;a!N2$9WA>CbfrWryqT25)S4E4)QXBd*3jN} z?phkAt`1rKW?xoLzEm!*IfkH|P>BtECVr0l8-IGk_`UjE#IWkUGqvyS+dMrCnFl<7RCgSMX^qn|Ld_4iYRldO zY&cHhv)GDo8nKvKwAbfyLR%t?9gG?R7~PSD#4D-;?F&!kV59O}neYut5AGbKwy-(U zqyBi=&Mgj|VIo>$u!DHM`R7O?W8-idbePuxiJMH``6c_5L-chKd}=rGC5Gfrc{f!* zWFEBm?l@_b7kzY7%1RQQbG5V<4=ZlkZ%sF74Q|mKOc7Ak7dP2#quiGcZ0_J%7Q?j{ zv9{WFw;n5G-Mn%r#0R;{jLt{yy}9J6rQ(>X9pJ`7Xy?Zv z=lNit#qXaq?CnElK^zF~sG}U5oCpR0T>FH=ZX}Prju$);?;VOhFH8L3I><9P_A|C+ z{;>~dk%9rrq(snjsEm}oUz2FQ21MCG*e?g)?{!&|eg7PX@I+Q0!hL6C7ZVY|g2E>i zr!Ri2@OfEu$)d52+>+cpgh6Z;cLYCZ&EMR0i<^~4&wEu_bdo;y^6}+U2GIQgW$|Od z_jg{O=pU>0-H$P-EOlWyQy#W0r@@_uT}Lg+!d5NxMii7aT1=|qm6BRaWOf{Pws54v zTu=}LR!V(JzI07>QR;;px0+zq=(s+XH-0~rVbmGp8<)7G+Jf)UYs<$Dd>-K+4}CsD zS}KYLmkbRvjwBO3PB%2@j(vOpm)!JABH_E7X^f#V-bzifSaKtE)|QrczC1$sC<<*Y z$hY*3E10fYk`2W09gM_U<2>+r^+ro$Bqh-O7uSa)cfPE_<#^O) zF+5V;-8LaCLKdIh3UB@idQZL`0Vx8`OE#6*1<;8(zi&E7MWB1S%~HAm%axyIHN2vd zA(pJGm_PraB0Aat3~?obWBs?iSc*NhM!{-l_WNCx4@F7I?)5&oI|z{o@JKd1HZ}zf*#}JjK3$ z-;3V*WJZvUcKvSOBH4c7C{fl8oRw8-vfgKQjNiR|KhQ%k6hWNEke(k8w-Ro| z7Y3)FsY-?7%;VT64vRM)l0%&HI~BXkSAOV#F3Bf#|3QLZM%6C{paqLTb3MU-_)`{R zRdfVQ)uX90VCa3ja$8m;cdtxQ*(tNjIfVb%#TCJWeH?o4RY#LWpyZBJHR| z6G-!4W5O^Z8U}e5GfZ!_M{B``ve{r0Z#CXV0x@~X#Pc;}{{ClY_uw^=wWurj0RKnoFzeY` z;gS!PCLCo*c}-hLc?C&wv&>P1hH75=p#;D3{Q8UZ0ctX!b)_@Ur=WCMEuz>pTs$@s z#7bIutL9Pm2FDb~d+H}uBI#pu6R}T{nzpz9U0XLb9lu@=9bTY&PEyFwhHHtXFX~6C zrcg|qqTk(|MIM%KQ<@j=DOjt|V)+8K26wE_CBNnZTg+Z+s}AU|jp6CFoIptG1{J*# z7Ne~l;ba*=bSwAMQ|Vq#fW~+je4PXA91YFzBubNF?ovIOw-$C-8=Ehed{lGD0}(Id zRe4sh8L>&T%{>8o))he}eE;5_ zxoXk3wX?MyNl-xF!q1d$G?=wp^`@09(jU&X zOqZIBI#dN`2PJNdATR3ivtub|nO$dulSaP|e4)WXF1YAGN1pDQIbIjXFG!oC85Mt; zW$eteoL{y^5t4TMRwP$jNPjZFpGsWnGe=jMMqKtcZm9Y9PFZLi*1p@qoKKub^T@2+ zk$@*KYdQ?Z`}<%4ALwk*Yc{(WTf@#u;as(fvE^9{Gk)lWbJP*SjttWofV0s?AB({~l zZI1hZVWFT~W-T?nfMMcnCS4-#6H-MU7H$KxD;yaM46K4Kc@~Q>xzB+QnD_I`b_l3m zo9pRx46b!p?a^&zCDwygqqV3epjs(s0NQI6ARA1n!Yy-qduipxQ& zUAlqRpNjBS+y-ZheD(!R;F}&^V_}b_gqH%tVZ5%%ziO7k^w=es+wZtK^i*vmrWNLMs{oWu_CIov|s1raZiS)>38>pYu;i+-t zI_DiNe6aA4KTZ2P09qPj(0~K4nUq^0+f(2$g`229zkG4jLzRvJUWE0oF1XHL4t3UN zDH466G56sy9hTZoAJB!C3;@F;ONxEk5u6Mv%zdo}Rq`=* zw1n7MOhfNSV48TS989ArIcj`C%Gk8~93~u>)!Yt2b4ZriKj9x2d`H2HQNJ=I>hkDlcZn zqRj>!;oRMTIOu zx|Zfsu~v76T{z7AC(jxj^c@tnJHZtGPsq$DE!8kqvkDx5W?KUJPL+!Ffpwfa+|5z5 zKPCiOPqZZrAG;2%OH0T$W|`C@C*!Z`@Wkop{CTjB&Tk`+{XPnt`ND`Haz;xV`H^RS zyXYtw@WlqTvToi;=mq1<-|IQ(gcOpU%)b#_46|IuWL#4$oYLbqwuk6=Q@xZaJSKVF zZcHs~ZBl;&lF3=+nK; zF`4gSCeZXlwmC_t4I`#PUNQ*)Uv&oGxMALip|sxv^lyVV73tKI7)+QY5=tEMas{vTD-BaTJ^*Y6gq~PU;F5X!sxqiq$iFCo+Uv7m%1w((=e}Vf*=dtds|6 zbX}91!G?C*KG03eHoN}RZS9DJxa&8YwNCT8?JxMXyZqZr13NA|GB{+vG`08C{V(yy zf*Lw$+tYSU_+dI`3n{bMrPdDb`A=Mkg!O=k>1|*3MC8j~- zXL79J4E=U^H=iBLTeHE_OKzE&dws8RNynsSJ!d;`zK?P92U{f)xvD7VQVosrXZrL+ z6lMVdD1YgL;%(1cq{#bS6yXmp|DS@nax#AqqlZhtUQdh<^2vr5`EpAO

    LGYq)sa(w9^3-f}NHy=GR4v%t2YZly3m1G@5y`xBh_HGrD%f z>;|Ty?9FiJAc&UVD(StT4I` zfVQwxhE9bXE6r2mKO8Ag7{L^jCyqQb0QqKDPE=RAgqn8q1O^>(z7h5kE(6va%QqRZ zkIOmp(})rLSS(2{=C12e&@!W2=Jel-^_R``0xHO^+t!(oXbcv5yhD4g*$t_F)_5Dl zSVCgesW%;DtYPCFs{G;GX_o?1J3;QQPPv)rWw;>} zJ&KwnUqwNXloNXlK_+pNDfI~hON#SokVJb&ilg8d7^NWo2ZQymCqQMnjfi>ePibjr z-Z@q!?RGN$Mj}Nk){X_vaj6?Mj$>ACR*z|6MsXy3VZ^PFn@yHkPo(>m(iWepn8SC@ z>D2;R4m+gDRZ=SIX!b+CP(qE=JDIUkn=D$aUu+Ihn9-+k1LS3PreQg0N5eWIG@x${nC3v^7caS>1!PKNAY9J z#}E}Q9w#SP>(GY7Hbj&z4$Li6o5taBO|4+F`yS9zq*LJ<38wy4I>HA9(&GYrk4dLajKGww))BWli6Ln1A^Lda@N~p+snkb9C z@OthI+<##vp8!HVQT4Wk(=@zQ{OvZ$EKWS73+JHb)eYLGD-cqi6^|vd$<+IHuc?Nq zW7JertT~3))4?J|28n$I@nAD0c1%9C&IVhEZX~mUsf{efyS(XNG%ch;!N~d7S(Ri7 zb&=BuON95aVA&kLn6&MVU|x}xPMp7xwWxNU1wS+F6#y}1@^wQZB*(&ecT?RnQcI}Y z2*z!^!D?gDUhc@;M^OpLs4mq>C&p{}OWVv<)S9KMars@0JQ{c_ScGsFo3BJ)Irg++ zAWwypJdTO-_{Uh8m(Z!3KL7K{ZZzKHj;{M8I$mV>k znTM?sa0);^=X^cglL`uC+^J)M7nEa$w=VwFULg~%DJllw+7dJAj3{qnP5i3@wr7%y zjXp?Wl2%Th=my&3u?Q$RV6N5tzKMSPTsc#J+-cDDp~qFB6bL2C8AS7Y3PKtVhdhl) zIaLqH5+OnWPWSt(lQCgkN8lczc-V%_iZ{>#1%Z$N*>lu#S;0MZ$T2Y8Kg!U;hAZj> z6S#%$DQ_`Ic%Zr@?}GgjRXg@qTj^17n`65oJ@Wj0u1X8&+UVd|Xs?J+i_^GZ94m6= zUc96~Q`OJvlKB_Lr15*Yw_PUPEr?f?H&00b^-W%26mD)(n(rGGNfK9~2h=C>p-7BZ zFd&*&Msdu{w~(eyFOglwCPH^Rb}O(N7LtS+nnEwDx*pGD?|&9Si~M43a+*L(b0$5A zv`T`(G3xO;I_sx;FwTP21ZlfDpz zOo?}Vlgf~fo{YWm@n_JyD*frOg{XsvBA~|Tn4V6hu>Gd>89-rblfVJUaGvj6X%NZ} z$tFF9sx=4_$*c~G`9iPLGh@=sV+O{D2-t*K@J7H=`V+oVt}8?04WwU3h1BgS!f%1P zFak-T#7`TtLcR=Yz>g0R!ZQrH!YiZOQN=_V-UyncN1Rc18?KY?#O`v#JK+pq0K$~H z3D@v9DZF42R)b9#BBX{^$DOMlJ!g)Gc za{o-1e%F6NvgKq9tC8pV+9S$;9*zNv{J*)n&dmf~anP1)4~N%~h#c(=B#3*KgzhCKhFdgDoWi2IDog{RVyzK|Y`rCUs3T~pJMmdZJy4?b z&s5G=zhf**(t7Y^oC_mcTsE-{^}wiaoUu&?kojLKs>SJPxjcP>{a5CbXCx92AcBE) zHtqP}LjZ{W>PH?Tu(E0X=%{PBMW@F_?#7b&#!^q`<-5$ur+-q6 z{dn=(^UZw6*3-XM_(=@<1_*i&XM4=0t5u!gm6 z{UlmNGPKgO_;e;q9|#esq~Sq`<}%d{+sRmhvsA{5i*91=tub>OZZ%)xUA#4q$dDyy z1`w4%?OPLg3JeZb#cqSMO?*Xn%|-FCcuH2i2fn_{IFusub6;NQdN|7TD1N?%E8*g? z$apAt@cEe!I%jB=*q$p_3=t_5R0ph%{qaq+QDg!c99Y!Xa!&oDZOeis_ot)gNXr{l zdY$|So2Qed2Y7KMNBrS^E169kG%h<+z{Z_p_;shB!uY)>yAVcK=&!bg`lVg)4T1|7 z0}7FpfydVH4F87K@c!nEG+WGKm{Ouo)Slpl;#qcEIQ0zdMfLA#;dBxYw;p;KoVv6| z3_D5&7rJdG12CnDSvZUW?$UC6^UVSW^|vw|o-_4bz)(w5(3AiVhpeT(|=f#x_}E?s#qHZF#xA6AF_ujl$G z-jHD%q(d2}v2PhXx&6YWps~m(^+RXl91Q#xRRJBhjKl$FG4bk);|ag;ieUZ&!Ii3$ z(iGz1+0m7#g5>ASldBbNZL=ZHh=tmmJt$!71; zIML2GhEz1pg@1rQN(M^_691wAGkJ@Pga_05WuQ6! zG5RkGY2^`@(H~pp7&Ga+Pwh3L!Njj!-rc;^bTIfo5hP@H##1X8xUZJckrx>id`bAd3QUx9GuomqBYZ!uN1-&o zvTxC?;p8vL67&fW8fw(YOqt>L@bdLrEF*3OgYe$4n4{ zEB40LiU#6-0@5jdN`0w}N0qi@c0~oT2FP z)LNk&a82my?jv(tQpiMi$TK_L@lub#lsM$R{Dk?Ya@%%%huZkct~tSWM714c!45k}-ZLVA-bVM`>|_ZBbW_m-7| z3U%xrAhi}n?T(2F{_n4EZ10inkIFl#y09?7$uwBoJgqY8vylwev)fDOn;>0R!aEnV zBz%j0Mqpx~EZU3q@%+oV7;}|vt7$~ou@faEIq{p?FY$XXg&6*K)b_LP=}gi9`Bij3 zN`zEo|B6*|-;>S`rNa^BKRDbDAk>X#MsR`EvL>6bqU@SaDDs z8>bu@3YdRaWs*Te@G-UHjU%F~kTHw5(0PVJ+pwh#ha2u;DB+UMo@A5UYIl#5rtBV- zGX_hIpw}3C@H*Us(Cc-d#-gNrG#w$(9+S=GxO>3SR`SE2fHZ2KrDc#_C^$jI>Y}#; zMwY=R6@+dWi~0RXw(c@3GZ&%~9K(q&ee0Zw;pwL`E_tZak-#8^_b)Dpyi73^he?xV zXJ08&wh5-M&}qy4f7!D&=E)puDD(Nmg1d_(j`4LvxM5x_huNg-pGG%9rYqO6mImyJ@}*3Y>^3OvcnTG%EV1) zq_Ap?Z!Iw__7#D=pOWnQN$gB!Mr0!9yx|g<4icJh{cFOu3B8}&RiYm+Mb;VEK``LK zL(NcpcTiGieOIssSjr?ob}^``nNf&UcJhXyncO9m{6gD$kqSD`S69(aF8dkWz5>!9 zBLe4Sib7Hs2x_L2Ls6Ish$MGVKrGt5+_2zCyP1byaCg3upo+-I}R4&$m)8 zQ7|jc1Z^VWggpuQj*cP;>Zo9LS!VSzrqmZczaf;u`d0J(f%Z9r%An@s!e>n9%y=n!IZ_tVGu{Jmsbp}Fk%HJIU?a+-~bjfLTuH|JExA8EROowzr zqW9{YyZhR0a4clRK>1I4Ncx&WER~{iE;F^$T7K%X@3PGOA%6#Z%p3TS^&M;Dnjw@i z^o!$9nhcsmcHcY4?4j9+ofL_CWsZ4Hcch(rjsGfGD(nsH>w}^ERqGnz%iGj0j{g}h z7wMkJ-2Z2~eS>2!i}0~B63i;>SyFJU2+>VCS^AxaDOx%g6-t0eM^P<3+*z`ztvOqrG3)&#$K?& z_Y0wbWID47@cU`E1A6A&!`aZk0ZE@z-h#l1NqX2#`$Uev2gepW`rf8*!=rD5&;Jb{ zl08rU>dPo=K%-1Ao1~G-@4ve~y5#9E8x;TE0k5d^TC(=Zc>mwjW^c=+U-<9}b0ku~}gj z3sbW>R2M6DR!g#NUP;nxo>)@7*=RP{U18SDop6b2&PHce^&h97@xx3t+VK+!keE#} z;(Uf&89as9k8{$nkLbuB!-d7TP`_VJpL^Xs8OKB~ri$YUbW8fch64}7|0EWoT(TRj{ z*GT<7Y<7DsrCi79ZsM)z#c(!nNOGySOCkY1fAuQOq12&iUVC!a`#O;dBLf=d?&4*B zI~LgAO7E0qxK(uRTM;IgJ}+z^gD+bi-6I!3x{r9`l~%8TRP%UE0V8E*Sz>Nl1NVG<<7(wDHZ+HcOkQm$O&k+vyx)y)x{Pz!U8hS$*m zByc0h6BUI*BOpuL==P+H|Hx%`>7!W+1H!l9vi&)`V zyn2o9{z=lc+VX*!Vh~SF=)L}Z40XeG>LF6cP^b+R$NxSeUqbK^Q*UTalKzP8X%{9@RSCXm_NhF>{=S2 zi}ezam_^P`S!!-cyEW9y7DBbK93roz@Raccy*v}?mKXScU9E_4g;hBU7}zSofAFda zKYEe?{{I54 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..e2847c82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..5bdb31f8 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (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. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # 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. 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. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, @@ -246,4 +249,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" +exec "$JAVACMD" "$@" \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..6042cb3e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -34,7 +36,7 @@ set APP_HOME=%DIRNAME% for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -89,4 +91,4 @@ exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal -:omega +:omega \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 594ed9ff..40384465 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,7 +17,7 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" } rootProject.name = "velocity" From af97ffffa586a7457312286c3270ed77bd1e49a7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 21 Dec 2024 03:45:17 -0500 Subject: [PATCH 04/34] 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 --- native/src/main/c/jni_cipher_macos.c | 9 ++++ native/src/main/c/jni_cipher_openssl.c | 9 ++++ .../encryption/JavaVelocityCipher.java | 9 ++++ .../velocitypowered/proxy/VelocityServer.java | 9 ++++ .../proxy/crypto/EncryptionUtils.java | 54 ------------------- 5 files changed, 36 insertions(+), 54 deletions(-) diff --git a/native/src/main/c/jni_cipher_macos.c b/native/src/main/c/jni_cipher_macos.c index aa7d1aba..d658eb5d 100644 --- a/native/src/main/c/jni_cipher_macos.c +++ b/native/src/main/c/jni_cipher_macos.c @@ -26,6 +26,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, 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; CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt, kCCModeCFB8, diff --git a/native/src/main/c/jni_cipher_openssl.c b/native/src/main/c/jni_cipher_openssl.c index 83515be5..06f09e11 100644 --- a/native/src/main/c/jni_cipher_openssl.c +++ b/native/src/main/c/jni_cipher_openssl.c @@ -32,6 +32,15 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, 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, encrypt); if (result != 1) { diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java index 58a7da58..00f5a1bf 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java @@ -48,6 +48,15 @@ public class JavaVelocityCipher implements VelocityCipher { private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException { 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, new IvParameterSpec(key.getEncoded())); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 8b5d4d3d..45c228b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -236,6 +236,15 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { 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); cm.logChannelInformation(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java index 283c8d49..04107b39 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java @@ -23,8 +23,6 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException; import it.unimi.dsi.fastutil.Pair; import java.io.IOException; import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; 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) { return MIME_SPECIAL_ENCODER.encodeToString(data); } @@ -155,22 +117,6 @@ public enum EncryptionUtils { 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 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. * From 1db8c8c6ab14a2309e936df917333fd4359621a0 Mon Sep 17 00:00:00 2001 From: Aaron <71191102+RealBauHD@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:59:13 +0100 Subject: [PATCH 05/34] Bump adventure to 4.18.0 (#1481) --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7557ed42..e3859eb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ shadow = "io.github.goooler.shadow:8.1.5" spotless = "com.diffplug.spotless:6.25.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.17.0" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.17.0" +adventure-bom = "net.kyori:adventure-bom:4.18.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.18.0" adventure-facet = "net.kyori:adventure-platform-facet:4.3.4" asm = "org.ow2.asm:asm:9.6" auto-service = "com.google.auto.service:auto-service:1.0.1" From 00b68859fffa31bc5bd66338e339131ddbb50ff5 Mon Sep 17 00:00:00 2001 From: TangJin <55261514+tangjin0418@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:53:53 +0800 Subject: [PATCH 06/34] Add "GetPlayerServer". (#1484) --- .../backend/BungeeCordMessageResponder.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index b047d186..6161c4c2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -301,6 +301,21 @@ public class BungeeCordMessageResponder { } } + private void processGetPlayerServer(ByteBufDataInput in) { + proxy.getPlayer(in.readUTF()).ifPresent(player -> { + 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 String getBungeeCordChannel(ProtocolVersion version) { return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL.getId() : LEGACY_CHANNEL.getId(); @@ -331,6 +346,9 @@ public class BungeeCordMessageResponder { ByteBufDataInput in = new ByteBufDataInput(message.content()); String subChannel = in.readUTF(); switch (subChannel) { + case "GetPlayerServer": + this.processGetPlayerServer(in); + break; case "ForwardToPlayer": this.processForwardToPlayer(in); break; From c0fdf20224d374927c49a628c3636f44b5245627 Mon Sep 17 00:00:00 2001 From: Gero Date: Wed, 15 Jan 2025 02:44:20 +0100 Subject: [PATCH 07/34] Add InboundConnection#getHandshakeIntent (#1493) * Add InboundConnection#getHandshakeIntent --- .../velocitypowered/api/proxy/InboundConnection.java | 8 ++++++++ .../proxy/connection/client/AuthSessionHandler.java | 2 +- .../proxy/connection/client/ConnectedPlayer.java | 10 +++++++++- .../connection/client/HandshakeSessionHandler.java | 5 +++++ .../connection/client/InitialInboundConnection.java | 6 ++++++ .../connection/client/LoginInboundConnection.java | 6 ++++++ 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index 1b16e9e1..884cd411 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -7,6 +7,7 @@ package com.velocitypowered.api.proxy; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import java.net.InetSocketAddress; @@ -60,4 +61,11 @@ public interface InboundConnection { * @return the protocol state of the connection */ ProtocolState getProtocolState(); + + /** + * Returns the initial intent for the connection. + * + * @return the intent of the connection + */ + HandshakeIntent getHandshakeIntent(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 6165a846..257429e2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -97,7 +97,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler { // Initiate a regular connection and move over to it. ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode, - inbound.getIdentifiedKey()); + inbound.getHandshakeIntent(), inbound.getIdentifiedKey()); this.connectedPlayer = player; if (!server.canRegisterConnection(player)) { player.disconnect0( diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 46c3d63d..a7717055 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -38,6 +38,7 @@ import com.velocitypowered.api.event.player.PlayerModInfoEvent; import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; 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.ProtocolVersion; import com.velocitypowered.api.permission.PermissionFunction; @@ -156,6 +157,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private final MinecraftConnection connection; private final @Nullable InetSocketAddress virtualHost; private final @Nullable String rawVirtualHost; + private final HandshakeIntent handshakeIntent; private GameProfile profile; private PermissionFunction permissionFunction; private int tryIndex = 0; @@ -193,12 +195,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, @Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode, - @Nullable IdentifiedKey playerKey) { + HandshakeIntent handshakeIntent, @Nullable IdentifiedKey playerKey) { this.server = server; this.profile = profile; this.connection = connection; this.virtualHost = virtualHost; this.rawVirtualHost = rawVirtualHost; + this.handshakeIntent = handshakeIntent; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; this.connectionPhase = connection.getType().getInitialClientPhase(); this.onlineMode = onlineMode; @@ -1335,6 +1338,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, return connection.getState().toProtocolState(); } + @Override + public HandshakeIntent getHandshakeIntent() { + return handshakeIntent; + } + private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final RegisteredServer toConnect; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index f31e0812..80260d2b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -277,5 +277,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { public ProtocolState getProtocolState() { return connection.getState().toProtocolState(); } + + @Override + public HandshakeIntent getHandshakeIntent() { + return HandshakeIntent.STATUS; + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java index 7b918374..35368844 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.connection.client; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.InboundConnection; @@ -98,6 +99,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection return connection.getState().toProtocolState(); } + @Override + public HandshakeIntent getHandshakeIntent() { + return handshake.getIntent(); + } + /** * Disconnects the connection from the server. * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java index 18596e4e..9922c509 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.connection.client; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.LoginPhaseConnection; @@ -177,4 +178,9 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi public ProtocolState getProtocolState() { return delegate.getProtocolState(); } + + @Override + public HandshakeIntent getHandshakeIntent() { + return delegate.getHandshakeIntent(); + } } From 71feb11b2ed143100fc5aeda771f28339fae354e Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Fri, 17 Jan 2025 15:10:06 +0000 Subject: [PATCH 08/34] Fix fallback compression handler --- .../natives/compression/JavaVelocityCompressor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java index 7182b352..b6c3aab4 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java @@ -57,7 +57,8 @@ public class JavaVelocityCompressor implements VelocityCompressor { inflater.setInput(source.nioBuffer()); try { - while (!inflater.finished() && inflater.getBytesWritten() < uncompressedSize) { + final int readable = source.readableBytes(); + while (!inflater.finished() && inflater.getBytesRead() < readable) { if (!destination.isWritable()) { destination.ensureWritable(ZLIB_BUFFER_SIZE); } From 7392cd6574fcc5b1496fe4c3ac2b6fa7c8e993d7 Mon Sep 17 00:00:00 2001 From: Henri Schubin Date: Tue, 21 Jan 2025 19:37:20 +0200 Subject: [PATCH 09/34] Fix nonsensical deprecation for specifying listener priority (#1491) * Fix nonsensical deprecation for specifying listener priority * Fix checkstyle error --- .../java/com/velocitypowered/api/event/PostOrder.java | 10 +++++++++- .../java/com/velocitypowered/api/event/Subscribe.java | 5 +---- .../proxy/event/VelocityEventManager.java | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/PostOrder.java b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java index 0d52ed2c..98fa2e86 100644 --- a/api/src/main/java/com/velocitypowered/api/event/PostOrder.java +++ b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java @@ -12,6 +12,14 @@ package com.velocitypowered.api.event; */ 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 } diff --git a/api/src/main/java/com/velocitypowered/api/event/Subscribe.java b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java index abb96c94..e0cdeb86 100644 --- a/api/src/main/java/com/velocitypowered/api/event/Subscribe.java +++ b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java @@ -32,12 +32,9 @@ public @interface Subscribe { * 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. * - *

    Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM} - * in order to use this field.

    - * * @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 diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java index 4c48068c..9d54b2d0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java @@ -350,8 +350,9 @@ public class VelocityEventManager implements EventManager { asyncType = AsyncType.ALWAYS; } + // The default value of 0 will fall back to PostOrder, the default PostOrder (NORMAL) is also 0 final short order; - if (subscribe.order() == PostOrder.CUSTOM) { + if (subscribe.priority() != 0) { order = subscribe.priority(); } else { order = (short) POST_ORDER_MAP.get(subscribe.order()); From 371e68607695b48117e1cd57d19c67e40384c33a Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 23 Jan 2025 19:29:37 +0000 Subject: [PATCH 10/34] properly apply vanilla cap to chat packets --- .../proxy/protocol/packet/chat/legacy/LegacyChatPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java index 4239489f..ccefdd13 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java @@ -91,7 +91,7 @@ public class LegacyChatPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - message = ProtocolUtils.readString(buf); + message = ProtocolUtils.readString(buf, 256); if (direction == ProtocolUtils.Direction.CLIENTBOUND && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { type = buf.readByte(); From 6995f415d3ca008f12af4625b83db5915c06c625 Mon Sep 17 00:00:00 2001 From: kyngs <38181667+kyngs@users.noreply.github.com> Date: Sun, 26 Jan 2025 13:49:35 +0100 Subject: [PATCH 11/34] Expose shutdownInProgress to the API. (#1485) Co-authored-by: kyngs --- .../java/com/velocitypowered/api/proxy/ProxyServer.java | 7 +++++++ .../java/com/velocitypowered/proxy/VelocityServer.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index f043b0c6..bbf999b2 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -41,6 +41,13 @@ public interface ProxyServer extends Audience { */ 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. * This includes the main minecraft listener and query channel. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 45c228b3..abf2cf00 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -803,6 +803,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { public VelocityChannelRegistrar getChannelRegistrar() { return channelRegistrar; } + + @Override + public boolean isShuttingDown() { + return shutdownInProgress.get(); + } @Override public InetSocketAddress getBoundAddress() { From 876b9c36012d6ed91ca7b7c0a747f6c2809ba1b3 Mon Sep 17 00:00:00 2001 From: Isaac - The456 Date: Mon, 27 Jan 2025 04:03:37 +0000 Subject: [PATCH 12/34] Fix ShutdownCommand message styling (#1427) * Fix ShutdownCommand message * Fix checkstyle violation. --- .../command/builtin/ShutdownCommand.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java index 402a0ce8..b60ead91 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.command.builtin; +import com.google.gson.JsonSyntaxException; import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.StringArgumentType; 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.proxy.ConsoleCommandSource; import com.velocitypowered.proxy.VelocityServer; +import net.kyori.adventure.text.Component; 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. @@ -53,11 +55,22 @@ public final class ShutdownCommand { StringArgumentType.greedyString()) .executes(context -> { String reason = context.getArgument("reason", String.class); - server.shutdown(true, MiniMessage.miniMessage().deserialize( - MiniMessage.miniMessage().serialize( - LegacyComponentSerializer.legacy('&').deserialize(reason) - ) - )); + Component reasonComponent = null; + + 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; }) ).build()); From 0e84b57e5387de45ce0021870b61045c4f5b43e5 Mon Sep 17 00:00:00 2001 From: scratchyone Date: Fri, 31 Jan 2025 07:04:52 -0500 Subject: [PATCH 13/34] 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 --- .../api/proxy/server/PingOptions.java | 29 +++++++++++++++++-- .../util/ServerListPingHandler.java | 6 ++-- .../proxy/server/PingSessionHandler.java | 7 +++-- .../server/VelocityRegisteredServer.java | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java b/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java index 51be358e..a3cd0ac1 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import net.kyori.adventure.builder.AbstractBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * 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(); private final ProtocolVersion protocolVersion; private final long timeout; + private final String virtualHost; private PingOptions(final Builder builder) { this.protocolVersion = builder.protocolVersion; this.timeout = builder.timeout; + this.virtualHost = builder.virtualHost; } /** @@ -54,6 +57,16 @@ public final class PingOptions { 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. * @@ -68,10 +81,9 @@ public final class PingOptions { if (o == null) { return false; } - if (!(o instanceof PingOptions)) { + if (!(o instanceof final PingOptions other)) { return false; } - final PingOptions other = (PingOptions) o; return Objects.equals(this.protocolVersion, other.protocolVersion) && Objects.equals(this.timeout, other.timeout); } @@ -97,6 +109,7 @@ public final class PingOptions { public static final class Builder implements AbstractBuilder { private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN; private long timeout = 0; + private String virtualHost = null; private Builder() { } @@ -146,6 +159,18 @@ public final class PingOptions { 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. * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 951d6098..97947eb6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -63,7 +63,7 @@ public class ServerListPingHandler { } private CompletableFuture attemptPingPassthrough(VelocityInboundConnection connection, - PingPassthroughMode mode, List servers, ProtocolVersion responseProtocolVersion) { + PingPassthroughMode mode, List servers, ProtocolVersion responseProtocolVersion, String virtualHostStr) { ServerPing fallback = constructLocalPing(connection.getProtocolVersion()); List> pings = new ArrayList<>(); for (String s : servers) { @@ -73,7 +73,7 @@ public class ServerListPingHandler { } VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get(); pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder() - .version(responseProtocolVersion).build())); + .version(responseProtocolVersion).virtualHost(virtualHostStr).build())); } if (pings.isEmpty()) { return CompletableFuture.completedFuture(fallback); @@ -155,7 +155,7 @@ public class ServerListPingHandler { .orElse(""); List serversToTry = server.getConfiguration().getForcedHosts().getOrDefault( virtualHostStr, server.getConfiguration().getAttemptConnectionOrder()); - return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion); + return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java index 89760344..e58c72b4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java @@ -43,20 +43,23 @@ public class PingSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection connection; private final ProtocolVersion version; private boolean completed = false; + private final String virtualHostString; PingSessionHandler(CompletableFuture result, RegisteredServer server, - MinecraftConnection connection, ProtocolVersion version) { + MinecraftConnection connection, ProtocolVersion version, String virtualHostString) { this.result = result; this.server = server; this.connection = connection; this.version = version; + this.virtualHostString = virtualHostString; } @Override public void activated() { HandshakePacket handshake = new HandshakePacket(); handshake.setIntent(HandshakeIntent.STATUS); - handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); + handshake.setServerAddress(this.virtualHostString == null || this.virtualHostString.isEmpty() + ? server.getServerInfo().getAddress().getHostString() : this.virtualHostString); handshake.setPort(server.getServerInfo().getAddress().getPort()); handshake.setProtocolVersion(version); connection.delayedWrite(handshake); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index a3ddd71c..697135e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -129,7 +129,7 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud if (future.isSuccess()) { MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); PingSessionHandler handler = new PingSessionHandler(pingFuture, - VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion()); + VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion(), pingOptions.getVirtualHost()); conn.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler); } else { pingFuture.completeExceptionally(future.cause()); From 91bdcebb1aefd7eb21ef88356e4dc88a3f697cb3 Mon Sep 17 00:00:00 2001 From: Outfluencer Date: Fri, 31 Jan 2025 14:11:39 +0100 Subject: [PATCH 14/34] Use real vanilla limits for LegacyChat (#1502) Client -> server < 1.11 has 100 >= 1.11 has 256 Server -> client has always 262144 --- .../proxy/protocol/packet/chat/legacy/LegacyChatPacket.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java index ccefdd13..80b239d5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java @@ -91,7 +91,8 @@ public class LegacyChatPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - message = ProtocolUtils.readString(buf, 256); + message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND + ? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100); if (direction == ProtocolUtils.Direction.CLIENTBOUND && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { type = buf.readByte(); From 6815808d327fa8d1a742099a4206d69ba7423764 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 31 Jan 2025 19:11:34 +0100 Subject: [PATCH 15/34] Improve fml mod list parsing --- api/src/main/java/com/velocitypowered/api/util/ModInfo.java | 2 ++ .../proxy/connection/forge/legacy/LegacyForgeUtil.java | 1 + 2 files changed, 3 insertions(+) diff --git a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java index 8a51f322..6c65782f 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java +++ b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java @@ -79,6 +79,8 @@ public final class ModInfo { public Mod(String id, String version) { this.id = Preconditions.checkNotNull(id, "id"); 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() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java index 1d066117..854a52d7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java @@ -64,6 +64,7 @@ class LegacyForgeUtil { if (discriminator == MOD_LIST_DISCRIMINATOR) { ImmutableList.Builder mods = ImmutableList.builder(); int modCount = ProtocolUtils.readVarInt(contents); + Preconditions.checkArgument(modCount < 1024, "Oversized mods list"); for (int index = 0; index < modCount; index++) { String id = ProtocolUtils.readString(contents); From 1652d44f5fd874e50285cffc5e791dc300e0d5b6 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 31 Jan 2025 19:35:43 +0100 Subject: [PATCH 16/34] =?UTF-8?q?Fix=20checkstyle=20=F0=9F=92=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/java/com/velocitypowered/api/util/ModInfo.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java index 6c65782f..cfc52289 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java +++ b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java @@ -76,6 +76,12 @@ public final class ModInfo { private final String id; 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) { this.id = Preconditions.checkNotNull(id, "id"); this.version = Preconditions.checkNotNull(version, "version"); From a26d5581c47bda30e34933fd176a3025e31c5ebc Mon Sep 17 00:00:00 2001 From: Kezz Date: Wed, 12 Feb 2025 19:35:15 +0000 Subject: [PATCH 17/34] docs: Remove beta annotations on events (#1505) These events aren't in beta, some of them have been stable for years now. --- .../api/event/command/PlayerAvailableCommandsEvent.java | 2 -- .../api/event/player/ServerPostConnectEvent.java | 2 -- .../api/event/proxy/server/ServerRegisteredEvent.java | 2 -- .../api/event/proxy/server/ServerUnregisteredEvent.java | 2 -- 4 files changed, 8 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/command/PlayerAvailableCommandsEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/PlayerAvailableCommandsEvent.java index 3f9b0298..d00cffac 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/PlayerAvailableCommandsEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/PlayerAvailableCommandsEvent.java @@ -9,7 +9,6 @@ package com.velocitypowered.api.event.command; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.event.annotation.AwaitingEvent; import com.velocitypowered.api.proxy.Player; @@ -21,7 +20,6 @@ import com.velocitypowered.api.proxy.Player; * client. */ @AwaitingEvent -@Beta public class PlayerAvailableCommandsEvent { private final Player player; diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerPostConnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerPostConnectEvent.java index 1be39544..95e70a49 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/ServerPostConnectEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerPostConnectEvent.java @@ -7,7 +7,6 @@ package com.velocitypowered.api.event.player; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; 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 * firing. */ -@Beta public class ServerPostConnectEvent { private final Player player; private final RegisteredServer previousServer; diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerRegisteredEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerRegisteredEvent.java index 754492a6..1ffb58bc 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerRegisteredEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerRegisteredEvent.java @@ -7,7 +7,6 @@ package com.velocitypowered.api.event.proxy.server; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.server.RegisteredServer; 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. * @since 3.3.0 */ -@Beta public record ServerRegisteredEvent(@NotNull RegisteredServer registeredServer) { public ServerRegisteredEvent { Preconditions.checkNotNull(registeredServer, "registeredServer"); diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerUnregisteredEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerUnregisteredEvent.java index 36b4023b..1d9548b7 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerUnregisteredEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/server/ServerUnregisteredEvent.java @@ -7,7 +7,6 @@ package com.velocitypowered.api.event.proxy.server; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.server.RegisteredServer; 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. * @since 3.3.0 */ -@Beta public record ServerUnregisteredEvent(@NotNull RegisteredServer unregisteredServer) { public ServerUnregisteredEvent { Preconditions.checkNotNull(unregisteredServer, "unregisteredServer"); From 83c1749eed3f550b54aa726e778dee1763201ac6 Mon Sep 17 00:00:00 2001 From: Andre_601 Date: Wed, 12 Feb 2025 20:35:38 +0100 Subject: [PATCH 18/34] Add javadoc for ServerPing.Builder (#1500) * Add javadoc for ServerPing.Builder * Address codestyle reports --- .../api/proxy/server/ServerPing.java | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index a7d9518f..f927228e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -159,31 +159,68 @@ 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) { this.version = Preconditions.checkNotNull(version, "version"); 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) { this.onlinePlayers = onlinePlayers; return this; } + /** + * Uses the modified {@code maximumPlayers} number in the response. + * This will not modify the actual maximum players that can join the server. + * + * @param maximumPlayers number for maximum players to set + * @return this builder, for chaining + */ public Builder maximumPlayers(int maximumPlayers) { this.maximumPlayers = maximumPlayers; return this; } + /** + * Uses the modified {@code players} array in the response. + * + * @param players array of SamplePlayers to set + * @return this builder, for chaining + */ public Builder samplePlayers(SamplePlayer... players) { this.samplePlayers.addAll(Arrays.asList(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) { this.modType = Preconditions.checkNotNull(modType, "modType"); 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) { this.mods.addAll(Arrays.asList(mods)); return this; @@ -193,7 +230,7 @@ public final class ServerPing { * Uses the modified {@code mods} list in the response. * * @param mods the mods list to use - * @return this build, for chaining + * @return this builder, for chaining */ public Builder mods(ModInfo mods) { Preconditions.checkNotNull(mods, "mods"); @@ -203,36 +240,74 @@ public final class ServerPing { return this; } + /** + * Clears the current list of mods to use in the response. + * + * @return this builder, for chaining + */ public Builder clearMods() { this.mods.clear(); return this; } + /** + * Clears the current list of PlayerSamples to use in the response. + * + * @return this builder, for chaining + */ public Builder clearSamplePlayers() { this.samplePlayers.clear(); return this; } + /** + * Defines the server as mod incompatible in the response. + * + * @return this builder, for chaining + */ public Builder notModCompatible() { this.nullOutModinfo = true; return this; } + /** + * Enables nulling Players in the response. + * This will display the player count as {@code ???}. + * + * @return this builder, for chaining + */ public Builder nullPlayers() { this.nullOutPlayers = true; return this; } + /** + * Uses the {@code description} Component in the response. + * + * @param description Component to use as the description. + * @return this builder, for chaining + */ public Builder description(net.kyori.adventure.text.Component description) { this.description = Preconditions.checkNotNull(description, "description"); 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) { this.favicon = Preconditions.checkNotNull(favicon, "favicon"); return this; } + /** + * Clears the current favicon used in the response. + * + * @return this builder, for chaining + */ public Builder clearFavicon() { this.favicon = null; return this; From 9d1332d3a3444814a77e7539935f27bd3d2755d4 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Tue, 11 Feb 2025 17:19:37 +0000 Subject: [PATCH 19/34] Add PluginMessageEvents for configuration phase --- .../backend/ConfigSessionHandler.java | 28 ++++++++++++++++- .../client/ClientConfigSessionHandler.java | 30 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 74f0576c..1860093d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -17,6 +17,7 @@ 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.player.CookieRequestEvent; 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.ServerResourcePackSendEvent; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -54,6 +56,8 @@ import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; @@ -261,7 +265,29 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), serverConn.getPlayer().getProtocolVersion())); } else { - serverConn.getPlayer().getConnection().write(packet.retain()); + byte[] bytes = ByteBufUtil.getBytes(packet.content()); + ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel()); + + if (id == null) { + 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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 7bb7bedf..a4e734ed 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -17,14 +17,17 @@ 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.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; 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.player.resourcepack.ResourcePackResponseBundle; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -41,6 +44,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -123,8 +127,32 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { brandChannel = packet.getChannel(); // Client sends `minecraft:brand` packet immediately after Login, // but at this time the backend server may not be ready + } else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) { + return true; } else if (serverConn != null) { - serverConn.ensureConnected().write(packet.retain()); + byte[] bytes = ByteBufUtil.getBytes(packet.content()); + ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel()); + + if (id == null) { + 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 + 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; } From f986eb51ecf341fe3eec2a14a1f3563f4a58af21 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 13 Feb 2025 12:12:33 +0000 Subject: [PATCH 20/34] Do not print an exception if a client closed before switching to config state --- .../proxy/connection/client/ConnectedPlayer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index a7717055..be7a82b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1287,6 +1287,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, public void switchToConfigState() { server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, getConnectionInFlightOrConnectedServer())) .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()) { bundleHandler.toggleBundleSession(); connection.write(BundleDelimiterPacket.INSTANCE); From e69213f9877d5b38c0e7f460b6133cf1ce7bfe83 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 17 Feb 2025 12:07:06 +0000 Subject: [PATCH 21/34] respect log-player-connections flag in config for connection messages (#1503) --- .../proxy/connection/client/AuthSessionHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 257429e2..0aea8d77 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -106,7 +106,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler { return CompletableFuture.completedFuture(null); } - logger.info("{} has connected", player); + if (server.getConfiguration().isLogPlayerConnections()) { + logger.info("{} has connected", player); + } return server.getEventManager() .fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) From 58816c804a80d781c3d734c8ad670e25959116c9 Mon Sep 17 00:00:00 2001 From: ST3V1K <56466580+ST3V1K@users.noreply.github.com> Date: Sat, 22 Feb 2025 17:08:37 +0100 Subject: [PATCH 22/34] fix: problem with PluginMessageEvents for configuration phase (#1517) --- .../proxy/connection/client/ClientConfigSessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index a4e734ed..a7143462 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -134,7 +134,7 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel()); if (id == null) { - serverConn.getPlayer().getConnection().write(packet.retain()); + serverConn.ensureConnected().write(packet.retain()); return true; } From 0afe0612247206275a0a2cdba0440ec7b4e46154 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Thu, 27 Feb 2025 01:25:01 -0500 Subject: [PATCH 23/34] Updated Adventure to 4.19.0 (#1520) Also updated ASM and Ansi to support Java 23 and 24 --- .../com/velocitypowered/api/command/CommandSource.java | 4 ++-- gradle/libs.versions.toml | 10 +++++----- .../main/java/com/velocitypowered/proxy/Metrics.java | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java index b3263639..3cc03925 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java @@ -27,7 +27,7 @@ public interface CommandSource extends Audience, PermissionSubject { * for more information on the format. **/ 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 TagResolver @NotNull... resolvers ) { - this.sendMessage(MiniMessage.miniMessage().deserialize(message, resolvers)); + this.sendMessage(MiniMessage.miniMessage().deserialize(message, this, resolvers)); } /** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e3859eb0..9d34ba9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,14 +11,14 @@ shadow = "io.github.goooler.shadow:8.1.5" spotless = "com.diffplug.spotless:6.25.0" [libraries] -adventure-bom = "net.kyori:adventure-bom:4.18.0" -adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.18.0" +adventure-bom = "net.kyori:adventure-bom:4.19.0" +adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.19.0" adventure-facet = "net.kyori:adventure-platform-facet:4.3.4" -asm = "org.ow2.asm:asm:9.6" +asm = "org.ow2.asm:asm:9.7.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" 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" checker-qual = "org.checkerframework:checker-qual:3.42.0" checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3" @@ -37,7 +37,7 @@ jline = "org.jline:jline-terminal-jansi:3.27.1" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" junit = "org.junit.jupiter:junit-jupiter:5.10.2" 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" gson = "com.google.code.gson:gson:2.10.1" guice = "com.google.inject:guice:6.0.0" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java index 89f63b56..7feeb25f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java @@ -65,7 +65,8 @@ public class Metrics { logger::info, config.isLogErrorsEnabled(), config.isLogSentDataEnabled(), - config.isLogResponseStatusTextEnabled() + config.isLogResponseStatusTextEnabled(), + false ); if (!config.didExistBefore()) { From d4ea40a4a27f352b1c9871dd7cb7edd3c64a6884 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 27 Feb 2025 23:42:39 -0500 Subject: [PATCH 24/34] Add support for `SO_REUSEPORT` --- .../proxy/config/VelocityConfiguration.java | 12 ++ .../proxy/network/ConnectionManager.java | 115 ++++++++++++------ .../src/main/resources/default-velocity.toml | 6 + 3 files changed, 93 insertions(+), 40 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 9c2e1e82..bbd49f55 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -407,6 +407,10 @@ public class VelocityConfiguration implements ProxyConfig { return forceKeyAuthentication; } + public boolean isEnableReusePort() { + return advanced.isEnableReusePort(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -716,6 +720,8 @@ public class VelocityConfiguration implements ProxyConfig { private boolean logPlayerConnections = true; @Expose private boolean acceptTransfers = false; + @Expose + private boolean enableReusePort = false; private Advanced() { } @@ -741,6 +747,7 @@ public class VelocityConfiguration implements ProxyConfig { this.logCommandExecutions = config.getOrElse("log-command-executions", false); this.logPlayerConnections = config.getOrElse("log-player-connections", true); this.acceptTransfers = config.getOrElse("accepts-transfers", false); + this.enableReusePort = config.getOrElse("enable-reuse-port", false); } } @@ -804,6 +811,10 @@ public class VelocityConfiguration implements ProxyConfig { return this.acceptTransfers; } + public boolean isEnableReusePort() { + return enableReusePort; + } + @Override public String toString() { return "Advanced{" @@ -821,6 +832,7 @@ public class VelocityConfiguration implements ProxyConfig { + ", logCommandExecutions=" + logCommandExecutions + ", logPlayerConnections=" + logPlayerConnections + ", acceptTransfers=" + acceptTransfers + + ", enableReusePort=" + enableReusePort + '}'; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 7af894f4..7b724f61 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -18,6 +18,8 @@ package com.velocitypowered.proxy.network; 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.ListenerCloseEvent; 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.ServerBootstrap; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.unix.UnixChannelOption; import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.MultithreadEventExecutorGroup; import java.net.InetSocketAddress; import java.net.http.HttpClient; -import java.util.HashMap; +import java.util.Collection; import java.util.Map; import org.apache.logging.log4j.LogManager; 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, 1 << 21); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); - private final Map endpoints = new HashMap<>(); + private final Multimap endpoints = HashMultimap.create(); private final TransportType transportType; private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; @@ -93,7 +98,6 @@ public final class ConnectionManager { public void bind(final InetSocketAddress address) { final ServerBootstrap bootstrap = new ServerBootstrap() .channelFactory(this.transportType.serverSocketChannelFactory) - .group(this.bossGroup, this.workerGroup) .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) .childHandler(this.serverChannelInitializer.get()) .childOption(ChannelOption.TCP_NODELAY, true) @@ -104,26 +108,50 @@ public final class ConnectionManager { bootstrap.option(ChannelOption.TCP_FASTOPEN, 3); } - bootstrap.bind() - .addListener((ChannelFutureListener) future -> { - final Channel channel = future.channel(); - if (future.isSuccess()) { - this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT)); - - // Warn people with console access that HAProxy is in use, see PR: #1436 - if (this.server.getConfiguration().isProxyProtocol()) { - LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress()); + 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 -> { + final Channel channel = future.channel(); + if (future.isSuccess()) { + 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 + if (this.server.getConfiguration().isProxyProtocol()) { + LOGGER.warn( + "Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", + channel.localAddress()); + } + + // Fire the proxy bound event after the socket is bound + server.getEventManager().fireAndForget( + new ListenerBoundEvent(address, ListenerType.MINECRAFT)); + } + } else { + LOGGER.error("Can't bind to {}", address, future.cause()); } + }); + f.syncUninterruptibly(); - LOGGER.info("Listening on {}", channel.localAddress()); - - // Fire the proxy bound event after the socket is bound - server.getEventManager().fireAndForget( - new ListenerBoundEvent(address, ListenerType.MINECRAFT)); - } else { - LOGGER.error("Can't bind to {}", address, future.cause()); - } - }); + if (!f.isSuccess()) { + break; + } + } } /** @@ -181,17 +209,20 @@ public final class ConnectionManager { * @param oldBind the endpoint to close */ public void close(InetSocketAddress oldBind) { - Endpoint endpoint = endpoints.remove(oldBind); + Collection 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 // 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(); - Channel serverChannel = endpoint.getChannel(); - - Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind); - LOGGER.info("Closing endpoint {}", serverChannel.localAddress()); - serverChannel.close().syncUninterruptibly(); + for (Endpoint endpoint : endpoints) { + Channel serverChannel = endpoint.getChannel(); + LOGGER.info("Closing endpoint {}", serverChannel.localAddress()); + serverChannel.close().syncUninterruptibly(); + } } /** @@ -200,24 +231,28 @@ public final class ConnectionManager { * @param interrupt should closing forward interruptions */ public void closeEndpoints(boolean interrupt) { - for (final Map.Entry entry : this.endpoints.entrySet()) { + for (final Map.Entry> entry : this.endpoints.asMap() + .entrySet()) { final InetSocketAddress address = entry.getKey(); - final Endpoint endpoint = entry.getValue(); + final Collection endpoints = entry.getValue(); + ListenerType type = endpoints.iterator().next().getType(); // 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. - server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join(); + server.getEventManager().fire(new ListenerCloseEvent(address, type)).join(); - LOGGER.info("Closing endpoint {}", address); - if (interrupt) { - try { - endpoint.getChannel().close().sync(); - } catch (final InterruptedException e) { - LOGGER.info("Interrupted whilst closing endpoint", e); - Thread.currentThread().interrupt(); + for (Endpoint endpoint : endpoints) { + LOGGER.info("Closing endpoint {}", address); + if (interrupt) { + try { + endpoint.getChannel().close().sync(); + } catch (final InterruptedException e) { + LOGGER.info("Interrupted whilst closing endpoint", e); + Thread.currentThread().interrupt(); + } + } else { + endpoint.getChannel().close().syncUninterruptibly(); } - } else { - endpoint.getChannel().close().syncUninterruptibly(); } } this.endpoints.clear(); diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index e402305c..5b0a1e27 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -145,6 +145,12 @@ log-player-connections = true # Transfer packet (Minecraft 1.20.5) to be received. accepts-transfers = false +# Enables support for SO_REUSEPORT. This may help the proxy scale better on multicore systems +# with a lot of incoming connections, and provide better CPU utilization than the existing +# strategy of having a single thread accepting connections and distributing them to worker +# threads. Disabled by default. Requires Linux or macOS. +enable-reuse-port = false + [query] # Whether to enable responding to GameSpy 4 query responses or not. enabled = false From b8fe3577c9582972a92134642e35eb7fac671376 Mon Sep 17 00:00:00 2001 From: Gero Date: Fri, 28 Feb 2025 15:01:20 +0100 Subject: [PATCH 25/34] Use correct component serializer in ComponentHolder#getBinaryTag --- .../proxy/protocol/packet/chat/ComponentHolder.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java index 0935f8ea..22b49621 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java @@ -43,7 +43,6 @@ import net.kyori.adventure.nbt.LongBinaryTag; import net.kyori.adventure.nbt.ShortBinaryTag; import net.kyori.adventure.nbt.StringBinaryTag; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -106,16 +105,14 @@ public class ComponentHolder { public BinaryTag getBinaryTag() { if (binaryTag == null) { // TODO: replace this with adventure-text-serializer-nbt - binaryTag = serialize(GsonComponentSerializer.gson().serializeToTree(getComponent())); + binaryTag = serialize(ProtocolUtils.getJsonChatSerializer(version).serializeToTree(getComponent())); } return binaryTag; } public static BinaryTag serialize(JsonElement json) { - if (json instanceof JsonPrimitive) { - JsonPrimitive jsonPrimitive = (JsonPrimitive) json; - - if (jsonPrimitive.isNumber()) { + if (json instanceof JsonPrimitive jsonPrimitive) { + if (jsonPrimitive.isNumber()) { Number number = json.getAsNumber(); if (number instanceof Byte) { From 8f3dea54277558ceade53eb19a9dbdb309685e09 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 28 Feb 2025 22:21:32 -0500 Subject: [PATCH 26/34] Bump Netty version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9d34ba9e..3057e031 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ configurate3 = "3.7.3" configurate4 = "4.1.2" flare = "2.0.1" log4j = "2.24.1" -netty = "4.1.114.Final" +netty = "4.1.119.Final" [plugins] indra-publishing = "net.kyori.indra.publishing:2.0.6" From f980037bfd87ec6c1891f33e0c7af7edb507bf37 Mon Sep 17 00:00:00 2001 From: Riley Park Date: Thu, 6 Mar 2025 19:14:18 -0800 Subject: [PATCH 27/34] chore(proxy): require explicitly setting velocity.command.info permission to true --- .../velocitypowered/proxy/command/builtin/VelocityCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 85bf2061..1e393476 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -82,7 +82,7 @@ public final class VelocityCommand { .executes(new Heap()) .build(); final LiteralCommandNode 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)) .build(); final LiteralCommandNode plugins = BrigadierCommand From c9aa1cca09313b71ead6309ecd85b59867df4183 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:11:01 +0100 Subject: [PATCH 28/34] Enable use tab in javadocs (#1525) --- api/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 2e524db9..a4cbd741 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -61,6 +61,7 @@ tasks { o.encoding = "UTF-8" o.source = "17" + o.use() o.links( "https://www.slf4j.org/apidocs/", "https://guava.dev/releases/${libs.guava.get().version}/api/docs/", From d9f1016bd57d50f5052bb90222894adb40fd256c Mon Sep 17 00:00:00 2001 From: Jones <73846784+jonesdevelopment@users.noreply.github.com> Date: Fri, 14 Mar 2025 10:26:59 -0500 Subject: [PATCH 29/34] Validate uncompressed packet size (#1527) * Validate uncompressed packet size * Fix debug using incorrect value --- .../proxy/protocol/netty/MinecraftCompressDecoder.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index 6e7fb4d4..5321b6ac 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -52,6 +52,9 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { int claimedUncompressedSize = ProtocolUtils.readVarInt(in); if (claimedUncompressedSize == 0) { + int actualUncompressedSize = in.readableBytes(); + checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than" + + " threshold %s", actualUncompressedSize, threshold); // This message is not compressed. out.add(in.retain()); return; From 4df640268f0877b5ff5ac4efa10b65147609e52b Mon Sep 17 00:00:00 2001 From: Gegy Date: Fri, 21 Mar 2025 13:28:13 +0100 Subject: [PATCH 30/34] 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) --- .../client/ClientPlaySessionHandler.java | 8 ++++++++ .../connection/client/ConnectedPlayer.java | 13 ++++++++++++- .../proxy/protocol/packet/chat/ChatQueue.java | 19 ++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index b41a24cc..8678431c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -170,6 +170,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public void deactivated() { + player.discardChatQueue(); for (PluginMessagePacket message : loginPluginMessages) { ReferenceCountUtil.release(message); } @@ -444,6 +445,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { 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 public void handleGeneric(MinecraftPacket packet) { VelocityServerConnection serverConnection = player.getConnectedServer(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index be7a82b2..ccd09218 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -190,7 +190,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private @Nullable Locale effectiveLocale; private final @Nullable IdentifiedKey playerKey; private @Nullable ClientSettingsPacket clientSettingsPacket; - private final ChatQueue chatQueue; + private volatile ChatQueue chatQueue; private final ChatBuilderFactory chatBuilderFactory; ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, @@ -236,6 +236,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, 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() { return this.bundleHandler; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java index 5928bf36..93ace258 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java @@ -32,13 +32,15 @@ import java.util.function.Function; * A precisely ordered queue which allows for outside entries into the ordered queue through * piggybacking timestamps. */ -public class ChatQueue { +public class ChatQueue implements AutoCloseable { private final Object internalLock = new Object(); private final ConnectedPlayer player; private final ChatState chatState = new ChatState(); private CompletableFuture head = CompletableFuture.completedFuture(null); + private volatile boolean closed; + /** * Instantiates a {@link ChatQueue} for a specific {@link ConnectedPlayer}. * @@ -50,8 +52,14 @@ public class ChatQueue { private void queueTask(Task task) { synchronized (internalLock) { + if (closed) { + throw new IllegalStateException("ChatQueue has already been closed"); + } MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected(); head = head.thenCompose(v -> { + if (closed) { + return CompletableFuture.completedFuture(null); + } try { return task.update(chatState, smc).exceptionally(ignored -> null); } catch (Throwable ignored) { @@ -102,9 +110,9 @@ public class ChatQueue { }); } - private static CompletableFuture writePacket(T packet, MinecraftConnection smc) { + private CompletableFuture writePacket(T packet, MinecraftConnection smc) { return CompletableFuture.runAsync(() -> { - if (!smc.isClosed()) { + if (!closed && !smc.isClosed()) { ChannelFuture future = smc.write(packet); if (future != null) { future.awaitUninterruptibly(); @@ -113,6 +121,11 @@ public class ChatQueue { }, smc.eventLoop()); } + @Override + public void close() { + closed = true; + } + private interface Task { CompletableFuture update(ChatState chatState, MinecraftConnection smc); } From d2cd79185b56bab1adbd45acb1caf0ea7f24d84e Mon Sep 17 00:00:00 2001 From: Aaron <71191102+RealBauHD@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:56:15 +0100 Subject: [PATCH 31/34] 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 Co-authored-by: Shane Freeder --- .../api/network/ProtocolVersion.java | 3 +- .../proxy/protocol/StateRegistry.java | 73 +++++++++++++------ .../packet/brigadier/ArgumentIdentifier.java | 7 ++ .../brigadier/ArgumentPropertyRegistry.java | 18 +++-- .../brigadier/RegistryKeyArgumentList.java | 14 ++-- .../proxy/protocol/packet/chat/ChatQueue.java | 2 +- .../packet/chat/LastSeenMessages.java | 32 +++++--- .../chat/session/SessionPlayerChatPacket.java | 4 +- .../session/SessionPlayerCommandPacket.java | 4 +- 9 files changed, 102 insertions(+), 55 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index d1eaa389..346a43c7 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -89,7 +89,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"), MINECRAFT_1_21(767, "1.21", "1.21.1"), 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, /*1073742067,*/ "1.21.5"); private static final int SNAPSHOT_BIT = 30; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 5a2fb8b8..e3539424 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -39,6 +39,7 @@ 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_2; 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_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -376,7 +377,8 @@ public enum StateRegistry { map(0x0D, MINECRAFT_1_17, false), map(0x0A, MINECRAFT_1_19, 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( LegacyChatPacket.class, LegacyChatPacket::new, @@ -397,7 +399,8 @@ public enum StateRegistry { map(0x0E, MINECRAFT_1_19, false), map(0x0D, MINECRAFT_1_19_3, 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( AvailableCommandsPacket.class, AvailableCommandsPacket::new, @@ -409,10 +412,12 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_19, false), map(0x0E, MINECRAFT_1_19_3, 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( 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( PluginMessagePacket.class, PluginMessagePacket::new, @@ -429,7 +434,8 @@ public enum StateRegistry { map(0x15, MINECRAFT_1_19_3, false), map(0x17, MINECRAFT_1_19_4, 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( DisconnectPacket.class, () -> new DisconnectPacket(this), @@ -446,7 +452,8 @@ public enum StateRegistry { map(0x17, MINECRAFT_1_19_3, false), map(0x1A, MINECRAFT_1_19_4, 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)); clientbound.register( KeepAlivePacket.class, KeepAlivePacket::new, @@ -464,7 +471,8 @@ public enum StateRegistry { map(0x23, MINECRAFT_1_19_4, false), map(0x24, MINECRAFT_1_20_2, 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)); clientbound.register( JoinGamePacket.class, JoinGamePacket::new, @@ -482,7 +490,8 @@ public enum StateRegistry { map(0x28, MINECRAFT_1_19_4, false), map(0x29, MINECRAFT_1_20_2, 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)); clientbound.register( RespawnPacket.class, RespawnPacket::new, @@ -503,13 +512,15 @@ public enum StateRegistry { map(0x43, MINECRAFT_1_20_2, true), map(0x45, MINECRAFT_1_20_3, 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)); clientbound.register( RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, map(0x43, MINECRAFT_1_20_3, 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)); clientbound.register( ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, @@ -530,7 +541,8 @@ public enum StateRegistry { map(0x42, MINECRAFT_1_20_2, false), map(0x44, MINECRAFT_1_20_3, 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)); clientbound.register( HeaderAndFooterPacket.class, HeaderAndFooterPacket::new, @@ -552,7 +564,8 @@ public enum StateRegistry { map(0x68, MINECRAFT_1_20_2, true), map(0x6A, MINECRAFT_1_20_3, 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)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -573,7 +586,8 @@ public enum StateRegistry { map(0x5F, MINECRAFT_1_20_2, true), map(0x61, MINECRAFT_1_20_3, 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)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -585,7 +599,8 @@ public enum StateRegistry { map(0x61, MINECRAFT_1_20_2, true), map(0x63, MINECRAFT_1_20_3, 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)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -597,7 +612,8 @@ public enum StateRegistry { map(0x48, MINECRAFT_1_20_2, true), map(0x4A, MINECRAFT_1_20_3, 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)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -609,7 +625,8 @@ public enum StateRegistry { map(0x62, MINECRAFT_1_20_2, true), map(0x64, MINECRAFT_1_20_3, 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)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -617,7 +634,8 @@ public enum StateRegistry { map(0x0D, MINECRAFT_1_19, true), map(0x0C, MINECRAFT_1_19_3, 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( LegacyPlayerListItemPacket.class, LegacyPlayerListItemPacket::new, @@ -637,7 +655,8 @@ public enum StateRegistry { map(0x39, MINECRAFT_1_19_4, false), map(0x3B, MINECRAFT_1_20_2, 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)); clientbound.register( UpsertPlayerInfoPacket.class, UpsertPlayerInfoPacket::new, @@ -645,11 +664,13 @@ public enum StateRegistry { map(0x3A, MINECRAFT_1_19_4, false), map(0x3C, MINECRAFT_1_20_2, 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)); clientbound.register( ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, 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)); clientbound.register( SystemChatPacket.class, SystemChatPacket::new, @@ -660,7 +681,8 @@ public enum StateRegistry { map(0x67, MINECRAFT_1_20_2, true), map(0x69, MINECRAFT_1_20_3, 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)); clientbound.register( PlayerChatCompletionPacket.class, PlayerChatCompletionPacket::new, @@ -668,7 +690,8 @@ public enum StateRegistry { map(0x14, MINECRAFT_1_19_3, true), map(0x16, MINECRAFT_1_19_4, 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( ServerDataPacket.class, ServerDataPacket::new, @@ -679,14 +702,16 @@ public enum StateRegistry { map(0x47, MINECRAFT_1_20_2, false), map(0x49, MINECRAFT_1_20_3, 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)); clientbound.register( StartUpdatePacket.class, () -> StartUpdatePacket.INSTANCE, map(0x65, MINECRAFT_1_20_2, false), map(0x67, MINECRAFT_1_20_3, 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)); clientbound.register( BundleDelimiterPacket.class, () -> BundleDelimiterPacket.INSTANCE, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java index 271d0c50..6441f6f7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java @@ -58,6 +58,13 @@ public class ArgumentIdentifier { this.versionById = ImmutableMap.copyOf(temp); } + @Override + public String toString() { + return "ArgumentIdentifier{" + + "identifier='" + identifier + '\'' + + '}'; + } + public String getIdentifier() { return identifier; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 7c04c97b..065de7b4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -22,6 +22,7 @@ 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_20_3; 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.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -254,17 +255,20 @@ public class ArgumentPropertyRegistry { register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY); + register(id("minecraft:resource_selector", 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_rotation", 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_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 + empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19 + empty(id("minecraft:template_rotation", 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_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_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), 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_predicate", mapSet(MINECRAFT_1_20_5, 51))); - empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_20_5, 52))); + empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50))); + empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51))); + empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52))); // Crossstitch support register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentList.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentList.java index fbd0b623..2bf5f345 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentList.java @@ -67,23 +67,23 @@ public final class RegistryKeyArgumentList { } } - public static class Resource extends RegistryKeyArgument { + public static class ResourceSelector extends RegistryKeyArgument { - public Resource(String identifier) { + public ResourceSelector(String identifier) { super(identifier); } - public static class Serializer implements ArgumentPropertySerializer { + public static class Serializer implements ArgumentPropertySerializer { - static final Resource.Serializer REGISTRY = new Resource.Serializer(); + static final ResourceSelector.Serializer REGISTRY = new ResourceSelector.Serializer(); @Override - public Resource deserialize(ByteBuf buf, ProtocolVersion protocolVersion) { - return new Resource(ProtocolUtils.readString(buf)); + public ResourceSelector deserialize(ByteBuf buf, ProtocolVersion protocolVersion) { + return new ResourceSelector(ProtocolUtils.readString(buf)); } @Override - public void serialize(Resource object, ByteBuf buf, ProtocolVersion protocolVersion) { + public void serialize(ResourceSelector object, ByteBuf buf, ProtocolVersion protocolVersion) { ProtocolUtils.writeString(buf, object.getIdentifier()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java index 93ace258..14a56336 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatQueue.java @@ -187,7 +187,7 @@ public class ChatQueue implements AutoCloseable { } public LastSeenMessages createLastSeen() { - return new LastSeenMessages(0, lastSeenMessages); + return new LastSeenMessages(0, lastSeenMessages, (byte) 0); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java index 18a743c8..c03e5f8c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/LastSeenMessages.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.protocol.packet.chat; +import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.Arrays; @@ -26,30 +27,38 @@ public class LastSeenMessages { public static final int WINDOW_SIZE = 20; private static final int DIV_FLOOR = -Math.floorDiv(-WINDOW_SIZE, 8); - private int offset; - private BitSet acknowledged; + private final int offset; + private final BitSet acknowledged; + private byte checksum; public LastSeenMessages() { - this.offset = 0; - this.acknowledged = new BitSet(); + this(0, new BitSet(), (byte) 0); } - public LastSeenMessages(int offset, BitSet acknowledged) { + public LastSeenMessages(int offset, BitSet acknowledged, byte checksum) { this.offset = offset; this.acknowledged = acknowledged; + this.checksum = checksum; } - public LastSeenMessages(ByteBuf buf) { + public LastSeenMessages(ByteBuf buf, ProtocolVersion protocolVersion) { this.offset = ProtocolUtils.readVarInt(buf); byte[] bytes = new byte[DIV_FLOOR]; buf.readBytes(bytes); this.acknowledged = BitSet.valueOf(bytes); + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + this.checksum = buf.readByte(); + } } - public void encode(ByteBuf buf) { + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { ProtocolUtils.writeVarInt(buf, offset); buf.writeBytes(Arrays.copyOf(acknowledged.toByteArray(), DIV_FLOOR)); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + buf.writeByte(this.checksum); + } } public int getOffset() { @@ -61,14 +70,15 @@ public class LastSeenMessages { } public LastSeenMessages offset(final int offset) { - return new LastSeenMessages(this.offset + offset, acknowledged); + return new LastSeenMessages(this.offset + offset, acknowledged, checksum); } @Override public String toString() { return "LastSeenMessages{" + - "offset=" + offset + - ", acknowledged=" + acknowledged + - '}'; + "offset=" + offset + + ", acknowledged=" + acknowledged + + ", checksum=" + checksum + + '}'; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java index 41f37366..8a00452c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java @@ -73,7 +73,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket { } else { this.signature = new byte[0]; } - this.lastSeenMessages = new LastSeenMessages(buf); + this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion); } @Override @@ -86,7 +86,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket { if (this.signed) { buf.writeBytes(this.signature); } - this.lastSeenMessages.encode(buf); + this.lastSeenMessages.encode(buf, protocolVersion); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java index 9084f4b5..993587dc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java @@ -45,7 +45,7 @@ public class SessionPlayerCommandPacket implements MinecraftPacket { this.timeStamp = Instant.ofEpochMilli(buf.readLong()); this.salt = buf.readLong(); this.argumentSignatures = new ArgumentSignatures(buf); - this.lastSeenMessages = new LastSeenMessages(buf); + this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion); } @Override @@ -54,7 +54,7 @@ public class SessionPlayerCommandPacket implements MinecraftPacket { buf.writeLong(this.timeStamp.toEpochMilli()); buf.writeLong(this.salt); this.argumentSignatures.encode(buf); - this.lastSeenMessages.encode(buf); + this.lastSeenMessages.encode(buf, protocolVersion); } public String getCommand() { From c3d10bd410e98d9be097bfcb459f8b35b3044041 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Sun, 30 Mar 2025 19:49:36 +0200 Subject: [PATCH 32/34] feat: add function to directly pass collections in ServerPing.Builder (#1538) --- .../api/proxy/server/ServerPing.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index f927228e..9a27dc05 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -14,6 +14,7 @@ import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.ModInfo; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -196,7 +197,7 @@ public final class ServerPing { /** * Uses the modified {@code players} array in the response. * - * @param players array of SamplePlayers to set + * @param players array of SamplePlayers to add * @return this builder, for chaining */ public Builder samplePlayers(SamplePlayer... players) { @@ -204,6 +205,17 @@ public final class ServerPing { 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 players) { + this.samplePlayers.addAll(players); + return this; + } + /** * Uses the modified {@code modType} in the response. * From cc93f5eea4f6aa7beb6608f7b79a6d9c1d65a945 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Sun, 30 Mar 2025 19:52:54 +0200 Subject: [PATCH 33/34] feat: improve tablist (#1532) * fix: setDisplayName in TabListEntry duplicating players on 1.7.10 (#1530) * feat: expose toggling hat layer in TabListEntry (#1531) --- .../api/proxy/player/TabList.java | 25 +++++++++++- .../api/proxy/player/TabListEntry.java | 40 +++++++++++++++++-- .../proxy/tablist/KeyedVelocityTabList.java | 2 +- .../proxy/tablist/VelocityTabList.java | 18 +++++++-- .../proxy/tablist/VelocityTabListEntry.java | 24 ++++++++++- .../tablist/VelocityTabListEntryLegacy.java | 4 +- .../proxy/tablist/VelocityTabListLegacy.java | 17 +++++++- 7 files changed, 118 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java index feffd76e..a4035530 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java @@ -40,6 +40,7 @@ public interface TabList { * Adds a {@link TabListEntry} to the {@link Player}'s 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); @@ -47,6 +48,7 @@ public interface TabList { * Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s 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 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. * * @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) { for (TabListEntry entry : entries) { @@ -187,6 +190,26 @@ public interface TabList { * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. */ @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, - int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder); + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index 350dc896..aea45287 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -160,6 +160,27 @@ public interface TabListEntry extends KeyIdentifiable { 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}. * @@ -183,6 +204,7 @@ public interface TabListEntry extends KeyIdentifiable { private int gameMode = 0; private boolean listed = true; private int listOrder = 0; + private boolean showHat; private @Nullable ChatSession chatSession; @@ -268,7 +290,7 @@ public interface TabListEntry extends KeyIdentifiable { * Sets whether this entry should be visible. * * @param listed to set - * @return ${code this}, for chaining + * @return {@code this}, for chaining * @see TabListEntry#isListed() */ 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. * * @param order to set - * @return ${code this}, for chaining + * @return {@code this}, for chaining * @sinceMinecraft 1.21.2 * @see TabListEntry#getListOrder() */ @@ -289,6 +311,18 @@ public interface TabListEntry extends KeyIdentifiable { 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}. * @@ -301,7 +335,7 @@ public interface TabListEntry extends KeyIdentifiable { if (profile == null) { 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); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java index daaa9b0a..644a7603 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java @@ -166,7 +166,7 @@ public class KeyedVelocityTabList implements InternalTabList { @Override public 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) { return buildEntry(profile, displayName, latency, gameMode, chatSession, listed); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index 0990119c..249bd3b7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -90,7 +90,7 @@ public class VelocityTabList implements InternalTabList { } else { entry = new VelocityTabListEntry(this, entry1.getProfile(), entry1.getDisplayNameComponent().orElse(null), - entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder()); + entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder(), entry1.isShowHat()); } EnumSet actions = EnumSet @@ -134,6 +134,11 @@ public class VelocityTabList implements InternalTabList { actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER); playerInfoEntry.setListOrder(entry.getListOrder()); } + if (!Objects.equals(previousEntry.isShowHat(), entry.isShowHat()) + && player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT); + playerInfoEntry.setShowHat(entry.isShowHat()); + } if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) { ChatSession from = entry.getChatSession(); if (from != null) { @@ -173,6 +178,10 @@ public class VelocityTabList implements InternalTabList { actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER); playerInfoEntry.setListOrder(entry.getListOrder()); } + if (!entry.isShowHat() && player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT); + playerInfoEntry.setShowHat(entry.isShowHat()); + } } return entry; }); @@ -218,9 +227,9 @@ public class VelocityTabList implements InternalTabList { @Override public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode, - @Nullable ChatSession chatSession, boolean listed, int listOrder) { + @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession, - listed, listOrder); + listed, listOrder, showHat); } @Override @@ -258,7 +267,8 @@ public class VelocityTabList implements InternalTabList { -1, null, false, - 0 + 0, + true ) ); } else { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java index 352d6271..96592dc2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -40,6 +40,7 @@ public class VelocityTabListEntry implements TabListEntry { private int gameMode; private boolean listed; private int listOrder; + private boolean showHat; private @Nullable ChatSession session; /** @@ -47,7 +48,7 @@ public class VelocityTabListEntry implements TabListEntry { */ public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency, - int gameMode, @Nullable ChatSession session, boolean listed, int listOrder) { + int gameMode, @Nullable ChatSession session, boolean listed, int listOrder, boolean showHat) { this.tabList = tabList; this.profile = profile; this.displayName = displayName; @@ -56,6 +57,7 @@ public class VelocityTabListEntry implements TabListEntry { this.session = session; this.listed = listed; this.listOrder = listOrder; + this.showHat = showHat; } @Override @@ -173,4 +175,24 @@ public class VelocityTabListEntry implements TabListEntry { void setListOrderWithoutUpdate(int listOrder) { this.listOrder = listOrder; } + + @Override + public boolean isShowHat() { + return showHat; + } + + @Override + public VelocityTabListEntry setShowHat(boolean showHat) { + this.showHat = showHat; + if (tabList.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + UpsertPlayerInfoPacket.Entry upsertEntry = this.tabList.createRawEntry(this); + upsertEntry.setShowHat(showHat); + tabList.emitActionRaw(UpsertPlayerInfoPacket.Action.UPDATE_HAT, upsertEntry); + } + return this; + } + + void setShowHatWithoutUpdate(boolean showHat) { + this.showHat = showHat; + } } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java index cd5e58db..56418d9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java @@ -35,6 +35,8 @@ public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry { @Override public TabListEntry setDisplayName(@Nullable Component displayName) { getTabList().removeEntry(getProfile().getId()); // We have to remove first if updating - return super.setDisplayName(displayName); + setDisplayNameInternal(displayName); + getTabList().addEntry(this); + return this; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java index 1eeb180e..a49d24de 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java @@ -19,6 +19,8 @@ package com.velocitypowered.proxy.tablist; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.crypto.IdentifiedKey; +import com.velocitypowered.api.proxy.player.ChatSession; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; @@ -133,9 +135,22 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList { } } + @Override + public TabListEntry buildEntry(GameProfile profile, + net.kyori.adventure.text.@Nullable Component displayName, + int latency, int gameMode, @Nullable IdentifiedKey key) { + return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); + } + @Override public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, - int gameMode) { + int gameMode, @Nullable ChatSession chatSession, boolean listed) { + return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); + } + + @Override + public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); } } From 9324a52ce0d0a474e93ee5c269a3e5b519423672 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Mon, 31 Mar 2025 19:58:43 +0200 Subject: [PATCH 34/34] fix: server link's custom labels not being translated (#1537) --- .../proxy/connection/client/ConnectedPlayer.java | 10 ++++++++-- .../packet/config/ClientboundServerLinksPacket.java | 6 ------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index ccd09218..7324544d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1101,8 +1101,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol"); } - connection.write(new ClientboundServerLinksPacket(List.copyOf(links).stream() - .map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList())); + connection.write(new ClientboundServerLinksPacket(links.stream() + .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 diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java index 274bbb8f..d37866d8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java @@ -68,12 +68,6 @@ public class ClientboundServerLinksPacket implements MinecraftPacket { public record ServerLink(int id, ComponentHolder displayName, String url) { - public ServerLink(com.velocitypowered.api.util.ServerLink link, ProtocolVersion protocolVersion) { - this(link.getBuiltInType().map(Enum::ordinal).orElse(-1), - link.getCustomLabel().map(c -> new ComponentHolder(protocolVersion, c)).orElse(null), - link.getUrl().toString()); - } - private static ServerLink read(ByteBuf buf, ProtocolVersion version) { if (buf.readBoolean()) { return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));