Compare commits

..

243 Commits
v1.0 ... main

Author SHA1 Message Date
Nan1t
a5b9e3510d Edit readme 2024-06-14 13:23:20 +03:00
Nan1t
155e6cc6c3 Renamed some keys in dimension codec. Increased version. 2024-06-14 13:21:58 +03:00
Nan1t
b99a533e1a Fixed spelling. Removed unnecessary var 2024-06-14 13:09:07 +03:00
Nan1t
94edb65dac Experimental support for 1.21 2024-06-14 12:51:09 +03:00
Nan1t
48346762bf Edit README.md 2024-05-02 17:36:57 +03:00
Max
402484c838
Update README.md 2024-05-02 17:31:57 +03:00
Nan1t
32b75fb9d8 Edit README.md 2024-05-02 16:55:03 +03:00
Nan1t
3f7c8bb1df Moved initial logging after config loaded 2024-05-02 16:38:56 +03:00
Nan1t
e9b6c888bc Added logback logging 2024-05-02 16:10:40 +03:00
Nan1t
6bbde1f578 Fixed spelling 2024-05-02 14:01:45 +03:00
Nan1t
f36f8dac6c Removed unused code 2024-05-02 13:52:42 +03:00
Max
f137495c4e
Merge pull request #75 from Pantera07/dev/better-limit
Better Packet Limiter
2024-05-02 13:44:55 +03:00
Max
04a646b271
Merge pull request #78 from jonesdevelopment/main
Optimize VarInt writing
2024-05-02 13:18:00 +03:00
Max
cd42e91cd1
Merge pull request #82 from BoomEaro/feature/1.20.5
Support 1.20.6
2024-05-02 13:03:01 +03:00
BoomEaro
e3e12db006
Update README.md 2024-04-30 23:36:48 +03:00
BoomEaro
a750bc50d2
Update README.md 2024-04-29 16:38:24 +03:00
BoomEaro
e54d8d6ed3
Fix wrong mappings for KeepAlive packet 2024-04-25 16:59:02 +03:00
BoomEaro
f5684107c8
Add support for 1.20.5 2024-04-25 14:06:07 +03:00
Pantera (Mad_Daniel)
ebe9c19a05
Increase maxPacketSize 2024-01-22 10:39:52 +09:00
Max
72774fbff9
Delete .github directory 2023-12-30 13:56:44 +02:00
Max
0489f883d1
Update README.md 2023-12-30 13:56:17 +02:00
Nan1t
6919420916 Update version 2023-12-30 13:46:08 +02:00
Max
dfa6cf3862
Update README.md 2023-12-30 13:44:29 +02:00
Max
93ad159833
Merge pull request #79 from BoomEaro/feature/1.20.3
Support 1.20.3
2023-12-30 13:42:50 +02:00
BoomEaro
f257426173
Use writePacket method 2023-12-06 18:37:28 +02:00
BoomEaro
4fd0389356
Bump netty 4.1.101.Final 2023-12-06 18:22:57 +02:00
BoomEaro
23bcfc7b20
Add support for 1.20.3 2023-12-06 18:21:54 +02:00
Michel Elkenwaat
9dc7027a22
feat: optimize VarInt writing (See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/) 2023-11-22 16:29:15 +01:00
Pantera (Mad_Daniel)
145d57679e
Better Packet Limiter
Reference: https://github.com/Spottedleaf/PacketLimiter
2023-10-11 19:00:57 +09:00
Nan1t
ff84c8f564 Update README 2023-10-01 14:07:45 +03:00
Nan1t
b5605f8d12 Remove unnecessary print 2023-10-01 13:59:46 +03:00
Nan1t
5615ec2321 Added experimental options to limit incoming packets 2023-10-01 13:58:08 +03:00
Nan1t
9490dc3ef2 Updated netty 2023-10-01 11:35:20 +03:00
Nan1t
7309ca3b41 Added generated version constant for 'version' command 2023-10-01 11:30:00 +03:00
Max
dceb977a75
Merge pull request #74 from BoomEaro/feature/1.20.2
Support 1.20.2
2023-10-01 11:03:37 +03:00
BoomEaro
9ce3146045 Add support for 1.20.2 2023-09-27 20:06:54 +03:00
Max
fb15336b42
Merge pull request #62 from Pantera07/info/1.20.1
Add version information for 1.20.1
2023-06-13 19:34:16 +03:00
Pantera (Mad_Daniel)
ddb27d421b
Add version information for 1.20.1
Add version information for 1.20.1
2023-06-13 00:08:20 +09:00
Nan1t
b301617f99 Edit README 2023-06-10 11:30:14 +03:00
Nan1t
e77a42ebc9 Changed version. Added additional info in the last comment 2023-06-10 11:13:01 +03:00
Nan1t
3ef5683222 Comment for new config param 2023-06-10 11:03:04 +03:00
Max
064150a171
Merge pull request #61 from Pantera07/dev/1.20
Update to 1.20
2023-06-10 10:50:36 +03:00
Pantera (Mad_Daniel)
6c1f7e4e2f
Bump adventure-nbt to 4.14.0 2023-06-08 00:50:23 +09:00
Max
ab1844131b
Merge pull request #57 from TehBrian/patch-1
clean up README.md wording
2023-05-31 12:15:35 +03:00
Nan1t
9558c2357b Added static protocol number option to support ViaVersion on the proxy side 2023-05-31 12:12:35 +03:00
Pantera (Mad_Daniel)
8d135c0804
Bump netty to 4.1.93.Final
https://netty.io/news/2023/05/25/4-1-93-Final.html
2023-05-30 01:58:42 +09:00
Pantera (Mad_Daniel)
0ddac89204
Support 1.20 2023-05-15 10:56:09 +09:00
TehBrian
97e4b44920
clean up README.md wording 2023-03-15 19:07:57 -04:00
Nan1t
dbeb7cc037 Edit README 2023-03-15 18:43:16 +03:00
Max
c96668f4d7
Merge pull request #56 from BoomEaro/feature/1.19.4
Support 1.19.4
2023-03-15 17:31:21 +02:00
Max
37813a1fed
Merge branch 'dev' into feature/1.19.4 2023-03-15 17:30:30 +02:00
BoomEaro
bbafbcc959 Update to 1.19.4 2023-03-15 15:44:55 +02:00
Nan1t
593e137b57 Mapped part of packets 2023-03-15 11:59:47 +03:00
Nan1t
21b74e8093 Changed version 2023-03-15 11:20:39 +03:00
Nan1t
2fdbd3e63a Added protocol verson 2023-03-15 11:18:34 +03:00
Nan1t
77a68efdc8 Renamed root packet 2022-12-11 13:36:24 +03:00
Nan1t
8210e61989 Added empty constructor for packet 2022-12-11 13:10:10 +03:00
Nan1t
0af7a4c63d Removed useless spawnPosition from config. Send spawn position and look snapshot depending on client version 2022-12-11 13:07:06 +03:00
Nan1t
3c9bb88e19 Added additional info in settings 2022-12-11 12:39:13 +03:00
Nan1t
05ef0435e5 Edit README 2022-12-11 11:58:20 +03:00
Nan1t
04c1730da0 Merge branch 'main' of github.com:Nan1t/NanoLimbo 2022-12-11 11:33:55 +03:00
Max
8fee9a4426
Merge pull request #45 from RaphiMC/main
Added 1.7.x support
2022-12-11 13:20:52 +02:00
Max
d5c61e8a8d
Merge branch 'main' into main 2022-12-11 13:14:22 +02:00
Max
f5003b1126
Merge pull request #46 from Pantera07/bump
Update dependencies
2022-12-11 11:57:39 +02:00
Max
353533a72a
Merge pull request #51 from BoomEaro/feature/1.19.3
Support 1.19.3
2022-12-11 11:53:45 +02:00
Nan1t
68846b0e0d Change version string 2022-12-11 11:33:46 +03:00
BoomEaro
68ea7c020c Add PacketSpawnPosition for 1.19.3 2022-12-08 15:05:46 +02:00
BoomEaro
b36decb3dd Support 1.19.3 2022-12-08 13:21:17 +02:00
Pantera (Mad_Daniel)
a327f7bc98
Bump adventure-nbt to 4.12.0
Bump adventure-nbt to 4.12.0
2022-12-01 12:23:27 +09:00
Pantera (Mad_Daniel)
714ef11fa8
Bump netty to 4.1.85.Final
Bump netty to 4.1.85.Final
2022-11-10 19:35:32 +09:00
Pantera (Mad_Daniel)
bc22bdd4ed
Update build.gradle 2022-10-18 00:32:22 +09:00
Max
5d08856bca
Merge pull request #41 from Pantera07/bump
Update build.gradle
2022-10-05 18:59:27 +03:00
RaphiMC
af304da0f0 Set gamemode to creative for 1.7.x instead of spectator 2022-10-01 00:11:56 +02:00
RK_01
747da18b72
Added 1.7.x to README 2022-09-30 18:50:03 +02:00
RaphiMC
4c8668f222 Added 1.7.x support 2022-09-30 18:49:23 +02:00
Pantera (Mad_Daniel)
699996e267
Bump netty to 4.1.82.Final
Bump netty to 4.1.82.Final
2022-09-16 13:45:25 +09:00
Pantera (Mad_Daniel)
e7257894e3
Update build.gradle 2022-08-28 10:01:28 +09:00
Max
b19fa9db37
Update README.md 2022-07-28 13:38:26 -05:00
Max
d474498ef6
Merge pull request #38 from BoomEaro/feature/1.19.1
Support 1.19.1
2022-07-28 02:21:08 -05:00
BoomEaro
b221d88d67 Support 1.19.1 2022-07-28 00:01:09 +03:00
Nanit
90364e4f6b Fixed JoinGame packet for 1.9.1 2022-07-07 12:32:35 +03:00
Nanit
fa63d3fff9 Probably fixed config for Pterodaktyl support 2022-07-07 12:16:36 +03:00
Nanit
b4daf6aa60 Changed version 2022-07-07 12:12:00 +03:00
Max
59d264d9b8
Merge pull request #31 from BBaoVanC/update-gradle
Update Gradle version to latest (7.4.2)
2022-07-07 04:09:34 -05:00
BBaoVanC
87debafbad
Update Gradle version to latest (7.4.2) 2022-07-02 19:30:52 -05:00
Nan1t
613f6e75b2 Trying to add .guthub folder 2022-07-02 06:51:41 -05:00
Nan1t
e0e5a4ae56 Added commands header 2022-07-01 17:52:04 -05:00
Nan1t
c8a3284c04 Added commands in README 2022-07-01 17:42:16 -05:00
Nan1t
e4f90b4de7 Added additional commands 2022-07-01 16:35:47 -05:00
Nanit
1f2558602c Edit javadoc 2022-07-01 12:55:00 +03:00
Nanit
70d3b58bbf Fixed command manager shutdown error. Code style 2022-07-01 12:37:52 +03:00
Nanit
577b7a239a Edit README 2022-06-30 22:31:36 +03:00
Nanit
66ec1404fd Moved command listener into separated thread 2022-06-30 22:28:53 +03:00
Nanit
f81a235982 Changed version string. Removed unused javadoc 2022-06-30 22:11:35 +03:00
Max
ad644b956a
Merge pull request #23 from kyngs/main
Add stop command and shutdown messages
2022-06-30 20:50:32 +02:00
Max
2548479cf4
Merge pull request #22 from BoomEaro/feature/1.18.2
Support 1.19
2022-06-30 20:41:11 +02:00
BoomEaro
c79b668a37 Always compile project to java 8 with UTF-8 encoding 2022-06-28 13:09:58 +03:00
BoomEaro
f8299d4e17 Fix accidental mapping change for PacketTitleLegacy 2022-06-26 17:53:49 +03:00
BoomEaro
1da0c8bc2c Don't cache PacketStatusResponse 2022-06-10 13:00:01 +03:00
BoomEaro
32ea23591f Fix dimension for 1.19 2022-06-09 19:12:53 +03:00
BoomEaro
226fd32c6d Remove unnecessary double reading 1.18.2 codec 2022-06-09 18:56:45 +03:00
BoomEaro
a3d4966150 Update dependencies 2022-06-09 15:16:35 +03:00
BoomEaro
8d1dca1334 Fix chat packet 2022-06-09 15:15:50 +03:00
BoomEaro
09cc1db851 Fix Header and Footer packet for 1.18 clients 2022-06-09 13:15:46 +03:00
BoomEaro
5d33193c65 Fix boss bar 2022-06-09 12:02:48 +03:00
BoomEaro
1db40eb6c4 Fix Player Info packet 2022-06-09 12:00:42 +03:00
BoomEaro
272c37bb79 Fix Join Game packet and Login packet, add new codec for 1.19 2022-06-09 11:47:12 +03:00
BoomEaro
ac8eda05ad Fix PacketLoginSuccess 2022-06-08 13:50:32 +03:00
BoomEaro
788c9ba4f2 Improve debugging 2022-06-08 12:08:38 +03:00
BoomEaro
32c95cc5cd Attempt fix PacketJoinGame 2022-06-08 11:50:00 +03:00
BoomEaro
b08599deda Add 1.19 mappings 2022-06-08 01:05:38 +03:00
kyngs
2cec5b8208 Add stop command and shutdown messages, so that the user knows, that something is happening :) 2022-03-11 11:15:05 +01:00
BoomEaro
dc6f85f6b7 Fix UNDEFINED packed encoding 2022-03-06 12:49:18 +02:00
BoomEaro
c4985c5b6b Change default spawnPosition height to 400 2022-03-05 23:36:36 +02:00
BoomEaro
0a98b8257f Remove empty chunk packet 2022-03-05 11:56:38 +02:00
BoomEaro
7fb11d2ffb Support 1.18.2 2022-03-04 12:47:19 +02:00
Nanit
9269751ed1 Reformatted packet handling. Moved packet snapshot in separated class 2022-02-07 18:10:40 +02:00
Nanit
6aea0392d4 Fixed some codefactor issues 2022-02-07 16:00:43 +02:00
Nanit
5a2049520c Merge branch 'main' of github.com:Nan1t/NanoLimbo 2022-02-07 14:44:26 +02:00
Nanit
e1445c006b Fixed playerlist for 1.16.5 clients. Changed version string 2022-02-07 14:39:08 +02:00
Max
3d166a137d
Update README.md 2022-02-07 12:07:11 +02:00
Nanit
021d0a18a5 Fixed logging 2022-02-07 11:31:37 +02:00
Nanit
f726a21e2c Removed useless 'nothing' debug level. Reordered levels indices. Moved Logger under server package and make setLevel method package-private 2022-02-07 11:23:16 +02:00
Nanit
4c2ad07049 Replace colors for header and footer 2022-01-31 21:18:05 +02:00
Nanit
aba8a6e1ed Updated Netty 2022-01-31 21:14:59 +02:00
Nanit
0958611aea Remove dockerfile 2022-01-31 21:12:08 +02:00
Nanit
647997aa5e Added HeaderAndFooter packet. Modified config file. Take username for playerlist not from version string 2022-01-31 21:11:43 +02:00
Max
416878d160
Merge pull request #18 from BoomEaro/fixes
Ability to disable players list. Fixed server starting message for log level < 3
2022-01-30 13:04:46 +02:00
BoomEaro
04c6e1b037 Always send packet PlayerInfo for <1.17 versions 2022-01-30 12:36:06 +02:00
BoomEaro
ba0292eb07 Remove declare commands from config 2022-01-30 12:17:54 +02:00
BoomEaro
5e1bf15fe1 Set declare commands empty 2022-01-30 12:17:20 +02:00
BoomEaro
188d2de0df Add configuration for declare commands and player list 2022-01-29 21:34:28 +02:00
BoomEaro
e089fb5584 Set custom log level after server started 2022-01-29 20:38:34 +02:00
Nanit
b77d9b0935 Changed version 2022-01-28 16:47:02 +02:00
Nanit
72cbf97019 Changed method signature 2022-01-28 16:14:09 +02:00
Nanit
c914a494e8 Moved exception throwing 2022-01-28 16:11:36 +02:00
Nanit
9670aeecbe Temporaly removed schematic-related code 2022-01-28 16:09:17 +02:00
Nanit
970d5cfcd2 Fixed packet snapshot for minimal version 2022-01-28 16:07:10 +02:00
Nanit
04a18a5386 Edit readme 2022-01-26 19:07:34 +02:00
Nanit
040e61ab72 Merge branch 'dev/1.4' 2022-01-26 18:53:19 +02:00
Nanit
297c1fa722 Temporaly commentet world-related code 2022-01-26 18:50:07 +02:00
Nanit
018d7b1e6f Returning max version if using ip forwarding 2022-01-26 18:47:31 +02:00
Nanit
aac321e182 Optimized packet snapshot 2022-01-26 17:20:54 +02:00
Nanit
be1fa775ec Release byte buf if hash not found 2022-01-24 21:08:51 +02:00
Nanit
1c3cb9b77d Optimized packet snapshot. Readers closing. Restructured schematic classes 2022-01-24 20:57:38 +02:00
Nanit
864a84b9b6 Added schematic containers and loader. Added world-related classes 2022-01-24 17:15:58 +02:00
Nanit
9f3fa80eef Added settings 2022-01-24 17:14:12 +02:00
Nanit
26c8dbba20 Added licenses to jar 2022-01-23 15:19:53 +02:00
Nanit
645e5c07ac Added GPL copyright messages to files 2022-01-23 13:30:10 +02:00
Nanit
088570b28e Edit readme 2022-01-23 13:23:53 +02:00
Nanit
cb02cc498b Restructured log levels. Made 'undefined packet' log as debug 2022-01-23 12:46:08 +02:00
Nanit
4bdf856363 Minor code optimizations 2022-01-23 12:25:39 +02:00
Nanit
1d75272c83 Updated deprecated NBT methods 2022-01-23 12:14:57 +02:00
Nanit
2e69580271 Renamed PreEncodedPacket. Removed unused classes 2022-01-23 12:08:57 +02:00
Max
f1f5d68dd6
Merge pull request #11 from MiGoYAm/main
Send PacketDeclareCommand only when limbo is connected to proxy
2021-12-30 20:08:23 +02:00
MiGoYAm
e6bcee3e73 fix 2021-12-23 19:00:48 +01:00
MiGoYAm
d49a16ba35 Send PacketDeclareCommand only when limbo is connected to proxy 2021-12-23 13:15:14 +01:00
Max
fd3d67bc07
Merge pull request #8 from MiGoYAm/main
Added display of brand name under F3
2021-12-12 21:21:25 +02:00
MiGoYAm
e6be30ba29 small improvement 2021-12-12 19:45:23 +01:00
MiGoYAm
d0d4d8a918 delete useless comment 2021-12-12 19:29:24 +01:00
MiGoYAm
6ed28c18e1 fix brandName's packet ids 2021-12-12 19:29:09 +01:00
MiGoYAm
7e467615ee check if packet is null 2021-12-12 18:55:38 +01:00
MiGoYAm
0d377ae0e3 optimize imports 2021-12-11 18:01:10 +01:00
MiGoYAm
22d8e4f6ad Revert "Update gradle wrapper"
This reverts commit a05523799df3f41a7be074e252367bce9a3c8102.
2021-12-11 17:48:58 +01:00
MiGoYAm
5a4889fb68 Revert "Optimize imports"
This reverts commit 4c5439082c23ad397bbfcc5521a6b8b00d356367.
2021-12-11 17:48:55 +01:00
MiGoYAm
eea304767e Revert "Optimize imports and update to java 17"
This reverts commit 3dfa56e1c53e770891ef07dd24bdcbfb8334b71e.
2021-12-11 17:48:50 +01:00
MiGoYAm
63ebb4e4af Revert "Optimize imports and delete command declaration packet"
This reverts commit 6d2ff1a6b9300d6b84bac6c448ed0f23bc75afb0.
2021-12-11 17:48:48 +01:00
MiGoYAm
b88263305b Revert "hotfix"
This reverts commit f1341ddba6e24211240d01478dce179164005833.
2021-12-11 17:48:46 +01:00
MiGoYAm
4ac087e391 Revert "optimize"
This reverts commit 0dda88d649ee14095587696f1400d6bdabc18964.
2021-12-11 17:48:45 +01:00
MiGoYAm
f23ee893fa Revert "Revert "Added display of brand name under f3""
This reverts commit 974e243402e0838db98716c7efba9da85b07bcf9.
2021-12-11 17:48:40 +01:00
MiGoYAm
d2c2987111 Revert "Revert "Added display of brand name under f3""
This reverts commit bcfbb2a7192754b6fb60634c5f8def12a96b3b07.
2021-12-11 17:48:38 +01:00
MiGoYAm
bcfbb2a719 Revert "Added display of brand name under f3"
This reverts commit 1d200dcfd9b885122e7ed9e161cd6615091ca760.
2021-12-11 17:47:27 +01:00
MiGoYAm
974e243402 Revert "Added display of brand name under f3"
This reverts commit 1d200dcfd9b885122e7ed9e161cd6615091ca760.

# Conflicts:
#	src/main/java/ru/nanit/limbo/protocol/registry/State.java
2021-12-11 16:55:09 +01:00
MiGoYAm
27ec7af95c add config section for brand name 2021-12-11 16:37:45 +01:00
MiGoYAm
016794cba8 extend compatibility for brand name 2021-12-11 15:08:36 +01:00
MiGoYAm
0dda88d649 optimize 2021-12-11 15:04:20 +01:00
MiGoYAm
f1341ddba6 hotfix 2021-12-10 17:30:05 +01:00
MiGoYAm
6d2ff1a6b9 Optimize imports and delete command declaration packet 2021-12-10 17:28:04 +01:00
MiGoYAm
3dfa56e1c5 Optimize imports and update to java 17 2021-12-10 17:24:07 +01:00
MiGoYAm
4c5439082c Optimize imports 2021-12-10 17:17:00 +01:00
MiGoYAm
a05523799d Update gradle wrapper 2021-12-10 17:16:46 +01:00
MiGoYAm
1d200dcfd9 Added display of brand name under f3 2021-12-10 17:00:08 +01:00
Max
2dfb952d71
Update README.md 2021-11-30 20:53:50 +02:00
Nanit
37e3a73d56 Edit readme 2021-11-30 20:43:52 +02:00
Nanit
10cfd3e625 Changed version string 2021-11-30 20:39:43 +02:00
Nanit
38300dbe1b Merge branch 'main' of github.com:Nan1t/NanoLimbo 2021-11-30 20:36:49 +02:00
Nanit
44c9aa28cd 1.18 beta support 2021-11-30 20:36:38 +02:00
Max
4c44efce05
Update README.md 2021-11-15 08:40:30 +02:00
Nanit
4c0f206afc Merge branch 'main' of github.com:Nan1t/NanoLimbo 2021-11-13 11:51:32 +02:00
Nanit
225a77a740 Changed version string 2021-11-13 11:51:21 +02:00
Nanit
207bf16b0c Added debug message 2021-11-13 11:50:39 +02:00
Nanit
14fd8f094c Added BungeeGuard support 2021-11-13 11:45:45 +02:00
Max
c0b8c5d331
Update README.md 2021-11-04 00:29:58 +02:00
Nanit
ebd6baf7b5 Ability to set infinite number of connections 2021-11-01 21:33:32 +02:00
Nanit
c6fcf0bf83 Edit settings file 2021-10-31 19:41:39 +02:00
Nanit
7694a69150 Edit readme 2021-10-31 19:41:28 +02:00
Nanit
07ee12931f Edit readme 2021-10-31 19:40:48 +02:00
Nanit
916b10e087 Fixed titles encoding 2021-10-31 19:13:30 +02:00
Nanit
790a1b414b Added join title 2021-10-31 18:48:13 +02:00
Nanit
24ccfd9050 Fixed codec name 2021-10-30 22:57:06 +03:00
Nanit
b08eea13e0 Modified dimension registry and join packet 2021-10-30 22:53:34 +03:00
Nanit
a439aa9847 Debug log about received packets 2021-10-30 20:20:41 +03:00
Nanit
1e4b4389d5 Fixed warning about unsipported version 2021-10-30 20:19:28 +03:00
Nanit
73f1045a76 Fixed for 1.17 support 2021-10-30 19:48:42 +03:00
Nanit
e45834c3bc Edit settings.yml 2021-10-30 19:04:03 +03:00
Nanit
d7b5101d50 Edit README 2021-10-30 18:58:38 +03:00
Nanit
b84720ef79 Edit README 2021-10-30 18:56:42 +03:00
Nanit
0527937bf0 Modified Chat packet for multiple versions 2021-10-30 18:51:09 +03:00
Nanit
5af7b9e2fc Edit readme 2021-10-30 18:35:46 +03:00
Nanit
e2f1317dc8 Edit readme 2021-10-30 15:37:53 +03:00
Nanit
e69e0c06f7 Edit readme 2021-10-30 15:35:34 +03:00
Nanit
e4c2c1f472 Fixed packet for 1.8 2021-10-30 15:26:40 +03:00
Nanit
515fd392c3 Fixed packed encoding 2021-10-30 15:08:07 +03:00
Nanit
aded28cfef Overrided toString method for packets 2021-10-30 13:58:19 +03:00
Nanit
7df4db9e10 PosAndLook packet for different evrsions 2021-10-30 13:47:23 +03:00
Nanit
a70a538ae5 Seems like PlayerInfo similar on all versions 2021-10-30 13:42:30 +03:00
Nanit
315b47ca7b Rewrite KeepAlive for supported versions 2021-10-30 13:27:24 +03:00
Nanit
e931773ce8 Rewrite JoinGame packet for supported versions 2021-10-30 13:23:03 +03:00
Nanit
cc5f92086a Moved refs to encoder and decoder to connection to speed up access 2021-10-29 13:57:01 +03:00
Nanit
574261ef1e Checks for client version 2021-10-29 13:50:34 +03:00
Nanit
a08ef52c63 Added 1.8 version mapping 2021-10-29 13:43:08 +03:00
Nanit
1ee11ffc66 Code style 2021-10-29 13:27:44 +03:00
Nanit
bcf8e0ebf2 Changed updateState method called only once to more suitable 2021-10-29 13:24:18 +03:00
Nanit
9659b4500a New helpers method for Version enum 2021-10-29 13:17:39 +03:00
Nanit
f490e00888 Modify state registry for multimple versions support 2021-10-29 13:12:43 +03:00
Nanit
e191c8f90b Preparing for multiple versions support 2021-10-28 22:24:01 +03:00
Nanit
62fbc0e8f2 Changed packet signature to support different versions 2021-10-28 21:55:59 +03:00
Nanit
ff5b605ee3 Removed unused code 2021-10-28 21:13:10 +03:00
Nanit
511ce43ada Renamed method 2021-10-28 21:09:09 +03:00
Nanit
d170bcbee2 Code style 2021-10-28 21:08:14 +03:00
Nanit
1c6da4ee0a Encoding != Rendering. Renamed confusing class name 2021-10-28 21:05:56 +03:00
Nanit
5c94dcffea Edit READE 2021-10-28 21:02:48 +03:00
Nanit
4507490c5a Changed version string 2021-10-28 20:56:31 +03:00
Nanit
ac337fa45e Removed unused dependency 2021-10-28 20:56:10 +03:00
Nanit
8ba8760925 Rewrite confguration and serializers to new config library 2021-10-28 20:53:46 +03:00
Nanit
168faf9eea Removed old configuration API, added new. Bit changed dependencies format 2021-10-28 20:22:16 +03:00
Nanit
c170aed44c Added Gradle wrapper. Edit .gitignore 2021-10-28 20:16:49 +03:00
Nanit
c445b07564 Migrate to Gradle 7+ 2021-10-28 20:14:45 +03:00
Nan1t
31f1772316 Changed version string 2020-12-27 20:09:00 +02:00
Nan1t
c083abae28 Fixed compatibility with OptiFine 2020-12-27 20:08:08 +02:00
Nanit
6d5c1628a9 Merge branch 'main' of github.com:Nan1t/NanoLimbo into main 2020-12-04 21:18:15 +02:00
Nanit
e1ebf4f609 Declarimg some commands to support proxy tab complete 2020-12-04 21:17:47 +02:00
Max
9dc456ba8f
Update README.md 2020-11-30 18:53:19 +02:00
134 changed files with 23676 additions and 2300 deletions

10
.gitignore vendored
View File

@ -1,7 +1,3 @@
/.idea/
/.gradle/
/gradle/
gradlew
gradlew.bat
build/
settings.yml
.idea
.gradle
build

105
README.md
View File

@ -1,66 +1,99 @@
## NanoLimbo
This is lightweight minecraft limbo server, written on Java with Netty.
The main goal of the project is maximum simplicity with a minimum number of sent and processed packets.
This limbo is empty, there are no ability to set schematic building since
this is not necessary. You can send useful information in chat or BossBar.
This is a lightweight Minecraft limbo server, written in Java with Netty.
The main goal of this project is maximum simplicity with a minimum number of sent and processed packets.
The limbo is empty; there is no ability to set a schematic building since this is not necessary.
You can send useful information via chat or boss bar.
No plugins, no logs. The server is fully clear. It only able keep a lot of players while the main server is down.
The server is fully clear. It is only able to keep a lot of players while the main server is down.
The general features:
* High performance. The server not saves and not cached any useless (for limbo) data.
General features:
* High performance. The server doesn't save or cache any useless (for limbo) data.
* Doesn't spawn threads per player. Use a fixed thread pool.
* Support for **BungeeCord** and **Velocity** info forwarding.
* All necessary data are configurable.
* Lightweight. App size less than **2MB.**
* Support for [BungeeGuard](https://www.spigotmc.org/resources/79601/) handshake format.
* Multiple versions support.
* Fully configurable.
* Lightweight. App size around **3MB**.
![](https://i.imgur.com/sT8p1Gz.png)
### Protocol support
### Versions support
The server written based on `1.16.4` minecraft protocol. You can use `ViaVersion`
or another protocol hack on your **proxy server** to achieve compatibility with other versions.
However, compatibility with other versions is in the plans, but not the nearest ones, since there
is a way to do this using a plugin that is installed on most networks.
Symbol `X` means all minor versions.
- [x] 1.7.X
- [x] 1.8.X
- [x] 1.9.X
- [x] 1.10.X
- [x] 1.11.X
- [x] 1.12.X
- [x] 1.13.X
- [x] 1.14.X
- [x] 1.15.X
- [x] 1.16.X
- [x] 1.17.X
- [x] 1.18.X
- [x] 1.19.X
- [x] 1.20.X
- [x] 1.21
The server **doesn't** support snapshots.
### Commands
There are no commands. To close server just run `Ctrl+C` in the terminal. It will be closed correctly.
* `help` - Show help message
* `conn` - Display number of connections
* `mem` - Display memory usage stats
* `stop` - Stop the server
Note that the server also will be closed correctly if you just press `Ctrl+C`.
### Installation
Required software: JRE 11+
The installation process is simple.
1. Download the latest version of program **[here](https://github.com/Nan1t/NanoLimbo/releases)**
2. Put jar file in the folder you want.
3. Create a start script as you did it for Bukkit/Spigot/etc. with command like this:
`java -jar <FileName>.jar`
4. The server will create `settings.yml` file. It's a server configuration.
5. Configure it as you want and restart server.
1. Download the latest version of the program [**here**](https://github.com/Nan1t/NanoLimbo/releases).
2. Put the jar file in the folder you want.
3. Create a start script as you did for Bukkit or BungeeCord, with a command like this:
`java -jar NanoLimbo-<version>.jar`
4. The server will create `settings.yml` file, which is the server configuration.
5. Configure it as you want and restart the server.
I recommend you to set parameter `debugLevel` to `0` when you finish testing server and run it into production.
This will disable some useless for production information in the console.
### Player info forwarding
### About player info forwarding
The server supports player info forwarding from the proxy. There are several types of info forwarding:
The server supports player info forwarding from the proxy. There are two type of info forwarding:
* `LEGACY` - The **BungeeCord** IP forwarding.
* `MODERN` - **Velocity** native info forwarding type.
* `BUNGEE_GUARD` - **BungeeGuard** forwarding type.
* LEGACY - The BungeeCord IP forwarding
* MODERN - Velocity native info forwarding type
If you use **BungeeCord**, or Velocity with LEGACY forwarding, just set this type in the config.
If you use **Velocity** with modern info forwarding, set this type and paste secret key from Velocity
config into `secret` field.
If you use BungeeCord, or Velocity with `LEGACY` forwarding, just set this type in the config.
If you use Velocity with `MODERN` info forwarding, set this type and paste the secret key from
Velocity config into `secret` field.
If you installed BungeeGuard on your proxy, then use `BUNGEE_GUARD` forwarding type.
Then add your tokens to `tokens` list.
### Contributing
You can create pull request, if you found some bug, optimization ability, or you wanna add some functional,
which will not significantly load the server.
Feel free to create a pull request if you find some bug or optimization opportunity, or if you want
to add some functionality that is suitable for a limbo server and won't significantly load the server.
### Building
The project uses `Gradle` to build and minimize output .jar file.
Run `shadowJar` task to build minimized executable jar file.
Required software:
* JDK 11+
* Gradle 7+ (optional)
To build a minimized jar, go to the project root directory and run in the terminal:
```
./gradlew shadowJar
```
### Contacts
If you have any question or suggestion, join to [Discord server](https://discord.gg/4VGP3Gv)
If you have any questions or suggestions, join our [Discord server](https://discord.gg/4VGP3Gv)!

View File

@ -1,29 +1,55 @@
plugins {
id 'com.github.johnrengelman.shadow' version '6.1.0'
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'com.github.gmazzo.buildconfig' version '3.1.0'
}
group 'ru.nanit'
version '1.2'
group 'ua.nanit'
version '1.8.1'
compileJava {
options.encoding = "UTF-8"
}
tasks.withType(JavaCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile files('libs/napi-configurate-yaml-1.0.jar')
compile group: 'org.yaml', name: 'snakeyaml', version: '1.27'
compile group: 'io.netty', name: 'netty-all', version: '4.1.54.Final'
compile group: 'net.kyori', name: 'adventure-nbt', version: '4.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3'
implementation 'ch.qos.logback:logback-classic:1.5.6'
implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'io.netty:netty-all:4.1.101.Final'
implementation 'net.kyori:adventure-nbt:4.14.0'
implementation 'com.grack:nanojson:1.8'
implementation 'com.google.code.gson:gson:2.10.1'
}
jar {
buildConfig {
className("BuildConfig")
packageName("ua.nanit.limbo")
buildConfigField('String', 'LIMBO_VERSION', "\"${project.version}\"")
}
shadowJar {
from 'LICENSE'
manifest {
attributes('Main-Class': 'ru.nanit.limbo.NanoLimbo')
attributes('Main-Class': 'ua.nanit.limbo.NanoLimbo')
}
minimize {
exclude(dependency('ch.qos.logback:logback-classic:.*:.*'))
}
}
shadowJar{
minimize()
test {
useJUnitPlatform()
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Executable file
View File

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

89
gradlew.bat vendored Normal file
View File

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

Binary file not shown.

View File

@ -1,9 +0,0 @@
package ru.nanit.limbo;
public final class LimboConstants {
public static final String VELOCITY_INFO_CHANNEL = "velocity:player_info";
private LimboConstants(){}
}

View File

@ -1,16 +0,0 @@
package ru.nanit.limbo;
import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger;
public final class NanoLimbo {
public static void main(String[] args){
try {
new LimboServer().start();
} catch (Exception e){
Logger.error("Cannot start server: ", e);
}
}
}

View File

@ -1,135 +0,0 @@
package ru.nanit.limbo.configuration;
import napi.configurate.Configuration;
import napi.configurate.source.ConfigSources;
import napi.configurate.yaml.YamlConfiguration;
import ru.nanit.limbo.server.data.*;
import ru.nanit.limbo.util.Colors;
import java.net.SocketAddress;
import java.nio.file.Path;
public final class LimboConfig {
private final Path root;
private SocketAddress address;
private int maxPlayers;
private PingData pingData;
private String dimensionType;
private Position spawnPosition;
private int gameMode;
private boolean useJoinMessage;
private boolean useBossBar;
private String joinMessage;
private BossBar bossBar;
private InfoForwarding infoForwarding;
private long readTimeout;
private int debugLevel = 3;
private boolean useEpoll;
private int bossGroupSize;
private int workerGroupSize;
public LimboConfig(Path root){
this.root = root;
}
public void load() throws Exception {
Configuration conf = YamlConfiguration.builder()
.source(ConfigSources.resource("/settings.yml", this).copyTo(root))
.build();
conf.reload();
address = conf.getNode("bind").getValue(SocketAddress.class);
maxPlayers = conf.getNode("maxPlayers").getInt();
pingData = conf.getNode("ping").getValue(PingData.class);
dimensionType = conf.getNode("dimension").getString();
spawnPosition = conf.getNode("spawnPosition").getValue(Position.class);
gameMode = conf.getNode("gameMode").getInt();
useJoinMessage = conf.getNode("joinMessage", "enable").getBoolean();
useBossBar = conf.getNode("bossBar", "enable").getBoolean();
if (useJoinMessage)
joinMessage = Colors.of(conf.getNode("joinMessage", "text").getString());
if (useBossBar)
bossBar = conf.getNode("bossBar").getValue(BossBar.class);
infoForwarding = conf.getNode("infoForwarding").getValue(InfoForwarding.class);
readTimeout = conf.getNode("readTimeout").getLong();
debugLevel = conf.getNode("debugLevel").getInt();
useEpoll = conf.getNode("netty", "useEpoll").getBoolean(true);
bossGroupSize = conf.getNode("netty", "threads", "bossGroup").getInt(1);
workerGroupSize = conf.getNode("netty", "threads", "workerGroup").getInt(4);
}
public SocketAddress getAddress() {
return address;
}
public int getMaxPlayers() {
return maxPlayers;
}
public PingData getPingData() {
return pingData;
}
public String getDimensionType() {
return dimensionType;
}
public Position getSpawnPosition() {
return spawnPosition;
}
public int getGameMode() {
return gameMode;
}
public InfoForwarding getInfoForwarding() {
return infoForwarding;
}
public long getReadTimeout() {
return readTimeout;
}
public int getDebugLevel() {
return debugLevel;
}
public boolean isUseJoinMessage() {
return useJoinMessage;
}
public boolean isUseBossBar() {
return useBossBar;
}
public String getJoinMessage() {
return joinMessage;
}
public BossBar getBossBar() {
return bossBar;
}
public boolean isUseEpoll() {
return useEpoll;
}
public int getBossGroupSize() {
return bossGroupSize;
}
public int getWorkerGroupSize() {
return workerGroupSize;
}
}

View File

@ -1,31 +0,0 @@
package ru.nanit.limbo.configuration;
import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer;
import napi.configurate.serializing.NodeSerializingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class SocketAddressSerializer implements NodeSerializer<SocketAddress> {
@Override
public SocketAddress deserialize(ConfigNode node) {
String ip = node.getNode("ip").getString();
int port = node.getNode("port").getInt();
SocketAddress address;
if (ip == null || ip.isEmpty()){
address = new InetSocketAddress(port);
} else {
address = new InetSocketAddress(ip, port);
}
return address;
}
@Override
public void serialize(SocketAddress socketAddress, ConfigNode configNode) throws NodeSerializingException {
}
}

View File

@ -1,36 +0,0 @@
package ru.nanit.limbo.connection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.ReadTimeoutHandler;
import ru.nanit.limbo.connection.pipeline.VarIntFrameDecoder;
import ru.nanit.limbo.connection.pipeline.PacketDecoder;
import ru.nanit.limbo.connection.pipeline.PacketEncoder;
import ru.nanit.limbo.connection.pipeline.VarIntLengthEncoder;
import ru.nanit.limbo.server.LimboServer;
import java.util.concurrent.TimeUnit;
public class ClientChannelInitializer extends ChannelInitializer<Channel> {
private final LimboServer server;
public ClientChannelInitializer(LimboServer server){
this.server = server;
}
@Override
protected void initChannel(Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("timeout", new ReadTimeoutHandler(server.getConfig().getReadTimeout(),
TimeUnit.MILLISECONDS));
pipeline.addLast("frame_decoder", new VarIntFrameDecoder());
pipeline.addLast("frame_encoder", new VarIntLengthEncoder());
pipeline.addLast("decoder", new PacketDecoder());
pipeline.addLast("encoder", new PacketEncoder());
pipeline.addLast("handler", new ClientConnection(channel, server));
}
}

View File

@ -1,336 +0,0 @@
package ru.nanit.limbo.connection;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import ru.nanit.limbo.LimboConstants;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PreRenderedPacket;
import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.*;
import ru.nanit.limbo.connection.pipeline.PacketDecoder;
import ru.nanit.limbo.connection.pipeline.PacketEncoder;
import ru.nanit.limbo.protocol.packets.PacketHandshake;
import ru.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger;
import ru.nanit.limbo.util.UuidUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
public class ClientConnection extends ChannelInboundHandlerAdapter {
private static PreRenderedPacket PACKET_LOGIN_SUCCESS;
private static PreRenderedPacket PACKET_JOIN_GAME;
private static PreRenderedPacket PACKET_PLAYER_ABILITIES;
private static PreRenderedPacket PACKET_PLAYER_INFO;
private static PreRenderedPacket PACKET_PLAYER_POS;
private static PreRenderedPacket PACKET_JOIN_MESSAGE;
private static PreRenderedPacket PACKET_BOSS_BAR;
private final LimboServer server;
private final Channel channel;
private final GameProfile gameProfile;
private State state;
private Version clientVersion;
private SocketAddress address;
private int velocityLoginMessageId = -1;
public ClientConnection(Channel channel, LimboServer server){
this.server = server;
this.channel = channel;
this.address = channel.remoteAddress();
this.gameProfile = new GameProfile();
}
public UUID getUuid() {
return gameProfile.getUuid();
}
public String getUsername(){
return gameProfile.getUsername();
}
public SocketAddress getAddress() {
return address;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY)){
server.getConnections().removeConnection(this);
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()){
Logger.error("Unhandled exception: ", cause);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
handlePacket(msg);
}
public void handlePacket(Object packet){
if (packet instanceof PacketHandshake){
PacketHandshake handshake = (PacketHandshake) packet;
clientVersion = handshake.getVersion();
updateState(State.getById(handshake.getNextState()));
Logger.debug("Pinged from " + address);
if (server.getConfig().getInfoForwarding().isLegacy()){
String[] split = handshake.getHost().split("\00");
if (split.length == 3 || split.length == 4){
setAddress(split[1]);
gameProfile.setUuid(UuidUtil.fromString(split[2]));
} else {
disconnectLogin("You've enabled player info forwarding. You need to connect with proxy");
}
}
return;
}
if (packet instanceof PacketStatusRequest){
sendPacket(new PacketStatusResponse(server));
return;
}
if (packet instanceof PacketStatusPing){
sendPacketAndClose(packet);
return;
}
if (packet instanceof PacketLoginStart){
if (server.getConnections().getCount() >= server.getConfig().getMaxPlayers()){
disconnectLogin("Too many players connected");
return;
}
if (!clientVersion.equals(Version.getCurrentSupported())){
disconnectLogin("Incompatible client version");
return;
}
if (server.getConfig().getInfoForwarding().isModern()){
velocityLoginMessageId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
PacketLoginPluginRequest request = new PacketLoginPluginRequest();
request.setMessageId(velocityLoginMessageId);
request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL);
request.setData(Unpooled.EMPTY_BUFFER);
sendPacket(request);
return;
}
if (!server.getConfig().getInfoForwarding().isModern()){
gameProfile.setUsername(((PacketLoginStart)packet).getUsername());
gameProfile.setUuid(UuidUtil.getOfflineModeUuid(getUsername()));
}
fireLoginSuccess();
return;
}
if (packet instanceof PacketLoginPluginResponse){
PacketLoginPluginResponse response = (PacketLoginPluginResponse) packet;
if (server.getConfig().getInfoForwarding().isModern()
&& response.getMessageId() == velocityLoginMessageId){
if (!response.isSuccessful() || response.getData() == null){
disconnectLogin("You need to connect with Velocity");
return;
}
if (!checkVelocityKeyIntegrity(response.getData())) {
disconnectLogin("Can't verify forwarded player info");
return;
}
// Order is important
setAddress(response.getData().readString());
gameProfile.setUuid(response.getData().readUuid());
gameProfile.setUsername(response.getData().readString());
fireLoginSuccess();
}
}
}
private void fireLoginSuccess(){
if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1){
disconnectLogin("You need to connect with Velocity");
return;
}
writePacket(PACKET_LOGIN_SUCCESS);
updateState(State.PLAY);
server.getConnections().addConnection(this);
writePacket(PACKET_JOIN_GAME);
writePacket(PACKET_PLAYER_ABILITIES);
writePacket(PACKET_PLAYER_POS);
writePacket(PACKET_PLAYER_INFO);
if (PACKET_BOSS_BAR != null)
writePacket(PACKET_BOSS_BAR);
if (PACKET_JOIN_MESSAGE != null)
writePacket(PACKET_JOIN_MESSAGE);
sendKeepAlive();
}
public void disconnectLogin(String reason){
if (isConnected() && state == State.LOGIN){
PacketDisconnect disconnect = new PacketDisconnect();
disconnect.setReason(reason);
sendPacketAndClose(disconnect);
}
}
public void sendKeepAlive(){
if (state.equals(State.PLAY)){
PacketKeepAlive keepAlive = new PacketKeepAlive();
keepAlive.setId(ThreadLocalRandom.current().nextLong());
sendPacket(keepAlive);
}
}
public void sendPacket(Object packet){
if (isConnected())
channel.writeAndFlush(packet, channel.voidPromise());
}
public void sendPacketAndClose(Object packet){
if (isConnected())
channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
public void writePacket(Object packet){
if (isConnected()) channel.write(packet, channel.voidPromise());
}
public void flushPackets(){
if (isConnected()) channel.flush();
}
public boolean isConnected(){
return channel.isActive();
}
private void updateState(State state){
this.state = state;
channel.pipeline().get(PacketDecoder.class).updateState(state);
channel.pipeline().get(PacketEncoder.class).updateState(state);
}
private void setAddress(String host){
this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort());
}
private boolean checkVelocityKeyIntegrity(ByteMessage buf) {
byte[] signature = new byte[32];
buf.readBytes(signature);
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(server.getConfig().getInfoForwarding().getSecretKey(), "HmacSHA256"));
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();
if (version != 1)
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001');
return true;
}
public static void preInitPackets(LimboServer server){
final String username = server.getConfig().getPingData().getVersion();
final UUID uuid = UuidUtil.getOfflineModeUuid(username);
PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUsername(username);
loginSuccess.setUuid(uuid);
PacketJoinGame joinGame = new PacketJoinGame();
joinGame.setEntityId(0);
joinGame.setEnableRespawnScreen(true);
joinGame.setFlat(false);
joinGame.setGameMode(server.getConfig().getGameMode());
joinGame.setHardcore(false);
joinGame.setMaxPlayers(server.getConfig().getMaxPlayers());
joinGame.setPreviousGameMode(-1);
joinGame.setReducedDebugInfo(true);
joinGame.setDebug(false);
joinGame.setViewDistance(2);
joinGame.setWorldName("minecraft:world");
joinGame.setWorldNames("minecraft:world");
joinGame.setHashedSeed(0);
joinGame.setDimensionCodec(server.getDimensionRegistry().getCodec());
joinGame.setDimension(server.getDimensionRegistry().getDefaultDimension());
PacketPlayerAbilities playerAbilities = new PacketPlayerAbilities();
playerAbilities.setFlyingSpeed(0.0F);
playerAbilities.setFlags(0x02);
playerAbilities.setFieldOfView(0.1F);
PacketPlayerPositionAndLook positionAndLook = new PacketPlayerPositionAndLook();
positionAndLook.setX(server.getConfig().getSpawnPosition().getX());
positionAndLook.setY(server.getConfig().getSpawnPosition().getY());
positionAndLook.setZ(server.getConfig().getSpawnPosition().getZ());
positionAndLook.setYaw(server.getConfig().getSpawnPosition().getYaw());
positionAndLook.setPitch(server.getConfig().getSpawnPosition().getPitch());
positionAndLook.setTeleportId(ThreadLocalRandom.current().nextInt());
PacketPlayerInfo info = new PacketPlayerInfo();
info.setUsername(username);
info.setGameMode(server.getConfig().getGameMode());
info.setUuid(uuid);
PACKET_LOGIN_SUCCESS = PreRenderedPacket.of(loginSuccess);
PACKET_JOIN_GAME = PreRenderedPacket.of(joinGame);
PACKET_PLAYER_ABILITIES = PreRenderedPacket.of(playerAbilities);
PACKET_PLAYER_POS = PreRenderedPacket.of(positionAndLook);
PACKET_PLAYER_INFO = PreRenderedPacket.of(info);
if (server.getConfig().isUseJoinMessage()){
PacketChatMessage joinMessage = new PacketChatMessage();
joinMessage.setJsonData(server.getConfig().getJoinMessage());
joinMessage.setPosition(PacketChatMessage.Position.CHAT);
joinMessage.setSender(UUID.randomUUID());
PACKET_JOIN_MESSAGE = PreRenderedPacket.of(joinMessage);
}
if (server.getConfig().isUseBossBar()){
PacketBossBar bossBar = new PacketBossBar();
bossBar.setBossBar(server.getConfig().getBossBar());
bossBar.setUuid(UUID.randomUUID());
PACKET_BOSS_BAR = PreRenderedPacket.of(bossBar);
}
}
}

View File

@ -1,25 +0,0 @@
package ru.nanit.limbo.connection;
import java.util.UUID;
public class GameProfile {
private UUID uuid;
private String username;
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -1,44 +0,0 @@
package ru.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.util.Logger;
import java.util.List;
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private State.PacketRegistry mappings;
public PacketDecoder(){
updateState(State.HANDSHAKING);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if (!ctx.channel().isActive() || mappings == null) return;
ByteMessage msg = new ByteMessage(buf);
int packetId = msg.readVarInt();
Packet packet = mappings.getPacket(packetId);
if (packet != null){
try {
packet.decode(msg);
} catch (Exception e){
Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
ctx.fireChannelRead(packet);
} else {
Logger.warning("Undefined incoming packet: 0x" + Integer.toHexString(packetId));
}
}
public void updateState(State state){
this.mappings = state.serverBound;
}
}

View File

@ -1,51 +0,0 @@
package ru.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.PreRenderedPacket;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.util.Logger;
public class PacketEncoder extends MessageToByteEncoder<Packet> {
private State.PacketRegistry registry;
public PacketEncoder(){
updateState(State.HANDSHAKING);
}
@Override
protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception {
if (registry == null) return;
ByteMessage msg = new ByteMessage(out);
int packetId;
if (packet instanceof PreRenderedPacket){
packetId = registry.getPacketId(((PreRenderedPacket)packet).getWrappedPacket().getClass());
} else {
packetId = registry.getPacketId(packet.getClass());
}
if (packetId == -1){
Logger.warning("Undefined packet class: %s", packet.getClass().getName());
return;
}
msg.writeVarInt(packetId);
try {
packet.encode(msg);
} catch (Exception e){
Logger.warning("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
public void updateState(State state){
this.registry = state.clientBound;
}
}

View File

@ -1,24 +0,0 @@
package ru.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ru.nanit.limbo.protocol.ByteMessage;
@ChannelHandler.Sharable
public class VarIntLengthEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) {
ByteMessage msg = new ByteMessage(out);
msg.writeVarInt(buf.readableBytes());
msg.writeBytes(buf);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) {
int anticipatedRequiredCapacity = 5 + msg.readableBytes();
return ctx.alloc().heapBuffer(anticipatedRequiredCapacity);
}
}

View File

@ -1,8 +0,0 @@
package ru.nanit.limbo.protocol;
public enum Direction {
CLIENT,
SERVER
}

View File

@ -1,9 +0,0 @@
package ru.nanit.limbo.protocol;
public interface Packet {
void encode(ByteMessage msg);
void decode(ByteMessage msg);
}

View File

@ -1,10 +0,0 @@
package ru.nanit.limbo.protocol;
public interface PacketIn extends Packet {
@Override
default void encode(ByteMessage msg) {
// Can be ignored for incoming packets
}
}

View File

@ -1,10 +0,0 @@
package ru.nanit.limbo.protocol;
public interface PacketOut extends Packet {
@Override
default void decode(ByteMessage msg) {
// Can be ignored for outgoing packets
}
}

View File

@ -1,32 +0,0 @@
package ru.nanit.limbo.protocol;
public class PreRenderedPacket implements PacketOut {
private final PacketOut packet;
private byte[] message;
public PreRenderedPacket(PacketOut packet){
this.packet = packet;
}
public PacketOut getWrappedPacket(){
return packet;
}
public PreRenderedPacket render(){
ByteMessage renderedMessage = ByteMessage.create();
packet.encode(renderedMessage);
this.message = renderedMessage.toByteArray();
renderedMessage.release();
return this;
}
@Override
public void encode(ByteMessage msg) {
msg.writeBytes(message);
}
public static PreRenderedPacket of(PacketOut packet){
return new PreRenderedPacket(packet).render();
}
}

View File

@ -1,61 +0,0 @@
package ru.nanit.limbo.protocol.packets;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketHandshake implements Packet {
private Version version;
private String host;
private int port;
private int nextState;
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getNextState() {
return nextState;
}
public void setNextState(int nextState) {
this.nextState = nextState;
}
@Override
public void encode(ByteMessage msg) {
msg.writeVarInt(this.version.getProtocolNumber());
msg.writeString(host);
msg.writeShort(port);
msg.writeVarInt(nextState);
}
@Override
public void decode(ByteMessage msg) {
this.version = Version.of(msg.readVarInt());
this.host = msg.readString();
this.port = msg.readUnsignedShort();
this.nextState = msg.readVarInt();
}
}

View File

@ -1,19 +0,0 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketDisconnect implements PacketOut {
private String reason;
public void setReason(String reason) {
this.reason = reason;
}
@Override
public void encode(ByteMessage msg) {
msg.writeString(String.format("{\"text\": \"%s\"}", reason));
}
}

View File

@ -1,32 +0,0 @@
package ru.nanit.limbo.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketLoginPluginRequest implements PacketOut {
private int messageId;
private String channel;
private ByteBuf data;
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public void setChannel(String channel) {
this.channel = channel;
}
public void setData(ByteBuf data) {
this.data = data;
}
@Override
public void encode(ByteMessage msg) {
msg.writeVarInt(messageId);
msg.writeString(channel);
msg.writeBytes(data);
}
}

View File

@ -1,35 +0,0 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketIn;
public class PacketLoginPluginResponse implements PacketIn {
private int messageId;
private boolean successful;
private ByteMessage data;
public int getMessageId() {
return messageId;
}
public boolean isSuccessful() {
return successful;
}
public ByteMessage getData() {
return data;
}
@Override
public void decode(ByteMessage msg) {
messageId = msg.readVarInt();
successful = msg.readBoolean();
if (msg.readableBytes() > 0){
int i = msg.readableBytes();
data = new ByteMessage(msg.readBytes(i));
}
}
}

View File

@ -1,18 +0,0 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.*;
public class PacketLoginStart implements PacketIn {
private String username;
public String getUsername() {
return username;
}
@Override
public void decode(ByteMessage msg) {
this.username = msg.readString();
}
}

View File

@ -1,27 +0,0 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
import java.util.UUID;
public class PacketLoginSuccess implements PacketOut {
private UUID uuid;
private String username;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public void encode(ByteMessage msg) {
msg.writeUuid(uuid);
msg.writeString(username);
}
}

View File

@ -1,38 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.server.data.BossBar;
import java.util.UUID;
public class PacketBossBar implements PacketOut {
private UUID uuid;
private BossBar bossBar;
private int flags;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setBossBar(BossBar bossBar) {
this.bossBar = bossBar;
}
public void setFlags(int flags) {
this.flags = flags;
}
@Override
public void encode(ByteMessage msg) {
msg.writeUuid(uuid);
msg.writeVarInt(0); // Create bossbar
msg.writeString(bossBar.getText());
msg.writeFloat(bossBar.getHealth());
msg.writeVarInt(bossBar.getColor().getIndex());
msg.writeVarInt(bossBar.getDivision().getIndex());
msg.writeByte(flags);
}
}

View File

@ -1,47 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
import java.util.UUID;
public class PacketChatMessage implements PacketOut {
private String jsonData;
private Position position;
private UUID sender;
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
public void setPosition(Position position) {
this.position = position;
}
public void setSender(UUID sender) {
this.sender = sender;
}
@Override
public void encode(ByteMessage msg) {
msg.writeString(jsonData);
msg.writeByte(position.index);
msg.writeUuid(sender);
}
public enum Position {
CHAT(0),
SYSTEM_MESSAGE(1),
ACTION_BAR(2);
private final int index;
Position(int index){
this.index = index;
}
}
}

View File

@ -1,104 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketJoinGame implements PacketOut {
private int entityId;
private boolean isHardcore = false;
private int gameMode = 2;
private int previousGameMode = -1;
private String[] worldNames;
private CompoundBinaryTag dimensionCodec;
private CompoundBinaryTag dimension;
private String worldName;
private long hashedSeed;
private int maxPlayers;
private int viewDistance = 2;
private boolean reducedDebugInfo;
private boolean enableRespawnScreen;
private boolean isDebug;
private boolean isFlat;
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public void setHardcore(boolean hardcore) {
isHardcore = hardcore;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setPreviousGameMode(int previousGameMode) {
this.previousGameMode = previousGameMode;
}
public void setWorldNames(String... worldNames) {
this.worldNames = worldNames;
}
public void setDimensionCodec(CompoundBinaryTag dimensionCodec) {
this.dimensionCodec = dimensionCodec;
}
public void setDimension(CompoundBinaryTag dimension) {
this.dimension = dimension;
}
public void setWorldName(String worldName) {
this.worldName = worldName;
}
public void setHashedSeed(long hashedSeed) {
this.hashedSeed = hashedSeed;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
this.enableRespawnScreen = enableRespawnScreen;
}
public void setDebug(boolean debug) {
isDebug = debug;
}
public void setFlat(boolean flat) {
isFlat = flat;
}
@Override
public void encode(ByteMessage msg) {
msg.writeInt(entityId);
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionCodec);
msg.writeCompoundTag(dimension);
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
}

View File

@ -1,28 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
public class PacketKeepAlive implements Packet {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public void encode(ByteMessage msg) {
msg.writeLong(id);
}
@Override
public void decode(ByteMessage msg) {
this.id = msg.readLong();
}
}

View File

@ -1,31 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketPlayerAbilities implements PacketOut {
private int flags = 0x02;
private float flyingSpeed = 0.0F;
private float fieldOfView = 0.1F;
public void setFlags(int flags) {
this.flags = flags;
}
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
}
public void setFieldOfView(float fieldOfView) {
this.fieldOfView = fieldOfView;
}
@Override
public void encode(ByteMessage msg) {
msg.writeByte(flags);
msg.writeFloat(flyingSpeed);
msg.writeFloat(fieldOfView);
}
}

View File

@ -1,41 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
import java.util.UUID;
/**
* This packet was very simplified and using only for ADD_PLAYER action
*/
public class PacketPlayerInfo implements PacketOut {
private int gameMode = 3;
private String username = "";
private UUID uuid;
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setUsername(String username){
this.username = username;
}
public void setUuid(UUID uuid){
this.uuid = uuid;
}
@Override
public void encode(ByteMessage msg) {
msg.writeVarInt(0);
msg.writeVarInt(1);
msg.writeUuid(uuid);
msg.writeString(username);
msg.writeVarInt(0);
msg.writeVarInt(gameMode);
msg.writeVarInt(60);
msg.writeBoolean(false);
}
}

View File

@ -1,55 +0,0 @@
package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut;
public class PacketPlayerPositionAndLook implements PacketOut {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
private byte flags = 0x08;
private int teleportId;
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void setZ(double z) {
this.z = z;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public void setFlags(byte flags) {
this.flags = flags;
}
public void setTeleportId(int teleportId) {
this.teleportId = teleportId;
}
@Override
public void encode(ByteMessage msg) {
msg.writeDouble(x);
msg.writeDouble(y);
msg.writeDouble(z);
msg.writeFloat(yaw);
msg.writeFloat(pitch);
msg.writeByte(flags);
msg.writeVarInt(teleportId);
}
}

View File

@ -1,20 +0,0 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
public class PacketStatusPing implements Packet {
private long randomId;
@Override
public void encode(ByteMessage msg) {
msg.writeLong(randomId);
}
@Override
public void decode(ByteMessage msg) {
this.randomId = msg.readLong();
}
}

View File

@ -1,12 +0,0 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.protocol.*;
public class PacketStatusRequest implements PacketIn {
@Override
public void decode(ByteMessage msg) {
}
}

View File

@ -1,32 +0,0 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.server.LimboServer;
public class PacketStatusResponse implements PacketOut {
private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }";
private LimboServer server;
public PacketStatusResponse(){ }
public PacketStatusResponse(LimboServer server){
this.server = server;
}
@Override
public void encode(ByteMessage msg) {
String ver = server.getConfig().getPingData().getVersion();
String desc = server.getConfig().getPingData().getDescription();
String json = getResponseJson(ver, Version.getCurrentSupported().getProtocolNumber(),
server.getConfig().getMaxPlayers(), server.getConnections().getCount(), desc);
msg.writeString(json);
}
private String getResponseJson(String version, int protocol, int maxPlayers, int online, String description){
return String.format(TEMPLATE, version, protocol, maxPlayers, online, description);
}
}

View File

@ -1,93 +0,0 @@
package ru.nanit.limbo.protocol.registry;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.packets.*;
import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.*;
import ru.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public enum State {
HANDSHAKING(0){
{
serverBound.register(0x00, PacketHandshake::new);
}
},
STATUS(1){
{
serverBound.register(0x01, PacketStatusPing::new);
serverBound.register(0x00, PacketStatusRequest::new);
clientBound.register(0x00, PacketStatusResponse::new);
clientBound.register(0x01, PacketStatusPing::new);
}
},
LOGIN(2){
{
serverBound.register(0x00, PacketLoginStart::new);
serverBound.register(0x02, PacketLoginPluginResponse::new);
clientBound.register(0x00, PacketDisconnect::new);
clientBound.register(0x02, PacketLoginSuccess::new);
clientBound.register(0x04, PacketLoginPluginRequest::new);
}
},
PLAY(3){
{
serverBound.register(0x10, PacketKeepAlive::new);
clientBound.register(0x24, PacketJoinGame::new);
clientBound.register(0x30, PacketPlayerAbilities::new);
clientBound.register(0x34, PacketPlayerPositionAndLook::new);
clientBound.register(0x1F, PacketKeepAlive::new);
clientBound.register(0x0E, PacketChatMessage::new);
clientBound.register(0x0C, PacketBossBar::new);
clientBound.register(0x32, PacketPlayerInfo::new);
}
};
private static final Map<Integer, State> STATE_BY_ID = new HashMap<>();
static {
for (State registry : values()){
STATE_BY_ID.put(registry.stateId, registry);
}
}
private final int stateId;
public final PacketRegistry serverBound = new PacketRegistry();
public final PacketRegistry clientBound = new PacketRegistry();
State(int stateId){
this.stateId = stateId;
}
public static State getById(int stateId){
return STATE_BY_ID.get(stateId);
}
public static class PacketRegistry {
private final Map<Integer, Supplier<?>> packetsById = new HashMap<>();
private final Map<Class<?>, Integer> packetIdByClass = new HashMap<>();
public Packet getPacket(int packetId){
Supplier<?> supplier = packetsById.get(packetId);
return supplier == null ? null : (Packet) supplier.get();
}
public int getPacketId(Class<?> packetClass){
return packetIdByClass.getOrDefault(packetClass, -1);
}
public void register(int packetId, Supplier<?> supplier){
packetsById.put(packetId, supplier);
packetIdByClass.put(supplier.get().getClass(), packetId);
}
}
}

View File

@ -1,64 +0,0 @@
package ru.nanit.limbo.protocol.registry;
import java.util.HashMap;
import java.util.Map;
public enum Version {
UNDEFINED(-1),
V1_9(107),
V1_9_1(108),
V1_9_2(109),
V1_9_4(110),
V1_10(210),
V1_11(315),
V1_11_1(316),
V1_12(335),
V1_12_1(338),
V1_12_2(340),
V1_13(393),
V1_13_1(401),
V1_13_2(404),
V1_14(477),
V1_14_1(480),
V1_14_2(485),
V1_14_3(490),
V1_14_4(498),
V1_15(573),
V1_15_1(575),
V1_15_2(578),
V1_16(735),
V1_16_1(736),
V1_16_2(751),
V1_16_3(753),
V1_16_4(754);
public static final Map<Integer, Version> VERSION_MAP;
static {
VERSION_MAP = new HashMap<>();
for (Version version : values()){
VERSION_MAP.put(version.getProtocolNumber(), version);
}
}
public static Version getCurrentSupported(){
return V1_16_4;
}
public static Version of(int protocolNumber){
return VERSION_MAP.getOrDefault(protocolNumber, UNDEFINED);
}
private final int protocolNumber;
Version(int protocolNumber){
this.protocolNumber = protocolNumber;
}
public int getProtocolNumber(){
return this.protocolNumber;
}
}

View File

@ -1,37 +0,0 @@
package ru.nanit.limbo.server;
import ru.nanit.limbo.connection.ClientConnection;
import ru.nanit.limbo.util.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public final class Connections {
private final Map<UUID, ClientConnection> connections;
public Connections(){
connections = new ConcurrentHashMap<>();
}
public Collection<ClientConnection> getAllConnections(){
return Collections.unmodifiableCollection(connections.values());
}
public int getCount(){
return connections.size();
}
public void addConnection(ClientConnection connection){
connections.put(connection.getUuid(), connection);
Logger.info("Player %s connected (%s)", connection.getUsername(), connection.getAddress());
}
public void removeConnection(ClientConnection connection){
connections.remove(connection.getUuid());
Logger.info("Player %s disconnected", connection.getUsername());
}
}

View File

@ -1,119 +0,0 @@
package ru.nanit.limbo.server.data;
import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer;
import napi.configurate.serializing.NodeSerializingException;
import ru.nanit.limbo.util.Colors;
public class BossBar {
private String text;
private float health;
private Color color;
private Division division;
public String getText() {
return text;
}
public float getHealth() {
return health;
}
public Color getColor() {
return color;
}
public Division getDivision() {
return division;
}
public void setText(String text) {
this.text = text;
}
public void setHealth(float health) {
this.health = health;
}
public void setColor(Color color) {
this.color = color;
}
public void setDivision(Division division) {
this.division = division;
}
public enum Color {
PINK(0),
BLUE(1),
RED(2),
GREEN(3),
YELLOW(4),
PURPLE(5),
WHITE(6);
private final int index;
Color(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
public enum Division {
SOLID(0),
DASHES_6(1),
DASHES_10(2),
DASHES_12(3),
DASHES_20(4);
private final int index;
Division(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
public static class Serializer implements NodeSerializer<BossBar>{
@Override
public BossBar deserialize(ConfigNode node) throws NodeSerializingException {
BossBar bossBar = new BossBar();
bossBar.setText(Colors.of(node.getNode("text").getString()));
bossBar.setHealth(node.getNode("health").getFloat());
if (bossBar.getHealth() < 0 || bossBar.getHealth() > 1)
throw new NodeSerializingException("BossBar health value must be between 0.0 and 1.0");
try {
bossBar.setColor(Color.valueOf(node.getNode("color").getString().toUpperCase()));
} catch (IllegalArgumentException e){
throw new NodeSerializingException("Invalid bossbar color");
}
try {
bossBar.setDivision(Division.valueOf(node.getNode("division").getString().toUpperCase()));
} catch (IllegalArgumentException e){
throw new NodeSerializingException("Invalid bossbar division");
}
return bossBar;
}
@Override
public void serialize(BossBar bossBar, ConfigNode configNode) {
}
}
}

View File

@ -1,65 +0,0 @@
package ru.nanit.limbo.server.data;
import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer;
import napi.configurate.serializing.NodeSerializingException;
import java.nio.charset.StandardCharsets;
public class InfoForwarding {
private Type type;
private byte[] secretKey;
public Type getType() {
return type;
}
public byte[] getSecretKey() {
return secretKey;
}
public boolean isNone(){
return type == Type.NONE;
}
public boolean isLegacy(){
return type == Type.LEGACY;
}
public boolean isModern(){
return type == Type.MODERN;
}
public enum Type {
NONE,
LEGACY,
MODERN
}
public static class Serializer implements NodeSerializer<InfoForwarding> {
@Override
public InfoForwarding deserialize(ConfigNode node) throws NodeSerializingException {
InfoForwarding forwarding = new InfoForwarding();
try {
forwarding.type = Type.valueOf(node.getNode("type").getString().toUpperCase());
} catch (IllegalArgumentException e){
throw new NodeSerializingException("Undefined info forwarding type");
}
if (forwarding.type == Type.MODERN){
forwarding.secretKey = node.getNode("secret").getString().getBytes(StandardCharsets.UTF_8);
}
return forwarding;
}
@Override
public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) {
}
}
}

View File

@ -1,43 +0,0 @@
package ru.nanit.limbo.server.data;
import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer;
import ru.nanit.limbo.util.Colors;
public class PingData {
private String version;
private String description;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static class Serializer implements NodeSerializer<PingData> {
@Override
public PingData deserialize(ConfigNode node) {
PingData pingData = new PingData();
pingData.setDescription(Colors.of(node.getNode("description").getString()));
pingData.setVersion(Colors.of(node.getNode("version").getString()));
return pingData;
}
@Override
public void serialize(PingData pingData, ConfigNode configNode) {
}
}
}

View File

@ -1,72 +0,0 @@
package ru.nanit.limbo.server.data;
import napi.configurate.data.ConfigNode;
import napi.configurate.serializing.NodeSerializer;
public class Position {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
public float getYaw() {
return yaw;
}
public float getPitch() {
return pitch;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void setZ(double z) {
this.z = z;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public static class Serializer implements NodeSerializer<Position> {
@Override
public Position deserialize(ConfigNode node) {
Position position = new Position();
position.setX(node.getNode("x").getDouble());
position.setY(node.getNode("y").getDouble());
position.setZ(node.getNode("z").getDouble());
position.setYaw(node.getNode("yaw").getFloat());
position.setPitch(node.getNode("pitch").getFloat());
return position;
}
@Override
public void serialize(Position position, ConfigNode configNode) {
}
}
}

View File

@ -1,14 +0,0 @@
package ru.nanit.limbo.util;
public final class Colors {
private static final char CHAR_FROM = '\u0026';
private static final char CHAR_TO = '\u00A7';
private Colors(){}
public static String of(String text){
return text.replace(CHAR_FROM, CHAR_TO);
}
}

View File

@ -1,79 +0,0 @@
package ru.nanit.limbo.util;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public final class Logger {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");
private static int debugLevel = 3;
private Logger(){}
public static void setLevel(int level){
debugLevel = level;
}
public static void info(Object msg, Object... args){
print(Level.INFO, msg, null, args);
}
public static void debug(Object msg, Object... args){
print(Level.DEBUG, msg, null, args);
}
public static void warning(Object msg, Object... args){
print(Level.WARNING, msg, null, args);
}
public static void warning(Object msg, Throwable t, Object... args){
print(Level.WARNING, msg, t, args);
}
public static void error(Object msg, Object... args){
print(Level.ERROR, msg, null, args);
}
public static void error(Object msg, Throwable t, Object... args){
print(Level.ERROR, msg, t, args);
}
public static void print(Level level, Object msg, Throwable t, Object... args){
if (debugLevel >= level.getIndex()){
System.out.println(String.format("%s: %s", getPrefix(level), String.format(msg.toString(), args)));
if (t != null) t.printStackTrace();
}
}
private static String getPrefix(Level level){
return String.format("[%s] [%s]", getTime(), level.getDisplay());
}
private static String getTime(){
return LocalTime.now().format(FORMATTER);
}
public enum Level {
INFO ("INFO", 0),
DEBUG ("DEBUG", 1),
WARNING("WARNING", 2),
ERROR("ERROR", 3);
private final String display;
private final int index;
Level(String display, int index){
this.display = display;
this.index = index;
}
public String getDisplay() {
return display;
}
public int getIndex() {
return index;
}
}
}

View File

@ -1,20 +0,0 @@
package ru.nanit.limbo.util;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public final class UuidUtil {
private UuidUtil(){}
public static UUID getOfflineModeUuid(String username){
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username)
.getBytes(StandardCharsets.UTF_8));
}
public static UUID fromString(String str){
if(str.contains("-")) return UUID.fromString(str);
return UUID.fromString(str.replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"));
}
}

View File

@ -1,171 +0,0 @@
package ru.nanit.limbo.world;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import ru.nanit.limbo.util.Logger;
public final class DimensionRegistry {
private CompoundBinaryTag defaultDimension;
private CompoundBinaryTag codec;
private CompoundBinaryTag overWorld;
private CompoundBinaryTag theEnd;
private CompoundBinaryTag nether;
public CompoundBinaryTag getCodec(){
return codec;
}
public CompoundBinaryTag getDefaultDimension() {
return defaultDimension;
}
public CompoundBinaryTag getOverWorld() {
return overWorld;
}
public CompoundBinaryTag getTheEnd() {
return theEnd;
}
public CompoundBinaryTag getNether() {
return nether;
}
public void load(String def){
initDimensions();
switch (def.toLowerCase()){
case "overworld":
defaultDimension = overWorld;
break;
case "nether":
defaultDimension = nether;
break;
case "the_end":
defaultDimension = theEnd;
break;
default:
defaultDimension = theEnd;
Logger.warning("Undefined dimension type: '%s'. Using THE_END as default", def);
break;
}
}
private void initDimensions(){
overWorld = CompoundBinaryTag.builder()
.putString("name", "minecraft:overworld")
.putByte("piglin_safe", (byte) 0)
.putByte("natural", (byte) 1)
.putFloat("ambient_light", 0.0F)
.putString("infiniburn", "minecraft:infiniburn_overworld")
.putByte("respawn_anchor_works", (byte) 0)
.putByte("has_skylight", (byte) 1)
.putByte("bed_works", (byte) 1)
.putString("effects", "minecraft:overworld")
.putLong("fixed_time", 6000L)
.putByte("has_raids", (byte) 1)
.putInt("logical_height", 256)
.putDouble("coordinate_scale", 1.0)
.putByte("ultrawarm", (byte) 0)
.putByte("has_ceiling", (byte) 0)
.build();
nether = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_nether")
.putByte("piglin_safe", (byte) 1)
.putByte("natural", (byte) 0)
.putFloat("ambient_light", 0.1F)
.putString("infiniburn", "minecraft:infiniburn_nether")
.putByte("respawn_anchor_works", (byte) 1)
.putByte("has_skylight", (byte) 0)
.putByte("bed_works", (byte) 0)
.putString("effects", "minecraft:the_nether")
.putLong("fixed_time", 18000L)
.putByte("has_raids", (byte) 0)
.putInt("logical_height", 128)
.putDouble("coordinate_scale", 1.0)
.putByte("ultrawarm", (byte) 1)
.putByte("has_ceiling", (byte) 1)
.build();
theEnd = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_end")
.putByte("piglin_safe", (byte) 0)
.putByte("natural", (byte) 0)
.putFloat("ambient_light", 0.0F)
.putString("infiniburn", "minecraft:infiniburn_end")
.putByte("respawn_anchor_works", (byte) 0)
.putByte("has_skylight", (byte) 0)
.putByte("bed_works", (byte) 0)
.putString("effects", "minecraft:the_end")
.putLong("fixed_time", 6000L)
.putByte("has_raids", (byte) 1)
.putInt("logical_height", 256)
.putDouble("coordinate_scale", 1.0)
.putByte("ultrawarm", (byte) 0)
.putByte("has_ceiling", (byte) 0)
.build();
CompoundBinaryTag overWorldData = CompoundBinaryTag.builder()
.putString("name", "minecraft:overworld")
.putInt("id", 0)
.put("element", overWorld)
.build();
CompoundBinaryTag netherData = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_nether")
.putInt("id", 1)
.put("element", nether)
.build();
CompoundBinaryTag endData = CompoundBinaryTag.builder()
.putString("name", "minecraft:the_end")
.putInt("id", 2)
.put("element", theEnd)
.build();
CompoundBinaryTag plains = CompoundBinaryTag.builder()
.putString("name", "minecraft:plains")
.putInt("id", 1)
.put("element", CompoundBinaryTag.builder()
.putString("precipitation", "rain")
.putFloat("depth", 0.125F)
.putFloat("temperature", 0.8F)
.putFloat("scale", 0.05F)
.putFloat("downfall", 0.4F)
.putString("category", "plains")
.put("effects", CompoundBinaryTag.builder()
.putInt("sky_color", 7907327)
.putInt("water_fog_color", 329011)
.putInt("fog_color", 12638463)
.putInt("water_color", 4159204)
.put("mood_sound", CompoundBinaryTag.builder()
.putInt("tick_delay", 6000)
.putFloat("offset", 2.0F)
.putString("sound", "minecraft:ambient.cave")
.putInt("block_search_extent", 8)
.build())
.build())
.build())
.build();
codec = CompoundBinaryTag.builder()
.put("minecraft:dimension_type", CompoundBinaryTag.builder()
.putString("type", "minecraft:dimension_type")
.put("value", ListBinaryTag.builder()
.add(overWorldData)
.add(netherData)
.add(endData)
.build())
.build())
.put("minecraft:worldgen/biome", CompoundBinaryTag.builder()
.putString("type", "minecraft:worldgen/biome")
.put("value", ListBinaryTag.builder()
.add(plains)
.build())
.build())
.build();
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo;
public final class LimboConstants {
public static final String VELOCITY_INFO_CHANNEL = "velocity:player_info";
public static final String BRAND_CHANNEL = "minecraft:brand";
private LimboConstants() {}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log;
public final class NanoLimbo {
public static void main(String[] args) {
try {
new LimboServer().start();
} catch (Exception e) {
Log.error("Cannot start server: ", e);
}
}
}

View File

@ -0,0 +1,279 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.configuration;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.serialize.TypeSerializerCollection;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import ua.nanit.limbo.util.Colors;
import ua.nanit.limbo.server.data.BossBar;
import ua.nanit.limbo.server.data.InfoForwarding;
import ua.nanit.limbo.server.data.PingData;
import ua.nanit.limbo.server.data.Title;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class LimboConfig {
private final Path root;
private SocketAddress address;
private int maxPlayers;
private PingData pingData;
private String dimensionType;
private int gameMode;
private boolean useBrandName;
private boolean useJoinMessage;
private boolean useBossBar;
private boolean useTitle;
private boolean usePlayerList;
private boolean useHeaderAndFooter;
private String brandName;
private String joinMessage;
private BossBar bossBar;
private Title title;
private String playerListUsername;
private String playerListHeader;
private String playerListFooter;
private InfoForwarding infoForwarding;
private long readTimeout;
private int debugLevel;
private boolean useEpoll;
private int bossGroupSize;
private int workerGroupSize;
private boolean useTrafficLimits;
private int maxPacketSize;
private double interval;
private double maxPacketRate;
public LimboConfig(Path root) {
this.root = root;
}
public void load() throws Exception {
ConfigurationOptions options = ConfigurationOptions.defaults().serializers(getSerializers());
YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
.source(this::getReader)
.defaultOptions(options)
.build();
ConfigurationNode conf = loader.load();
address = conf.node("bind").get(SocketAddress.class);
maxPlayers = conf.node("maxPlayers").getInt();
pingData = conf.node("ping").get(PingData.class);
dimensionType = conf.node("dimension").getString("the_end");
if (dimensionType.equalsIgnoreCase("nether")) {
dimensionType = "the_nether";
}
if (dimensionType.equalsIgnoreCase("end")) {
dimensionType = "the_end";
}
gameMode = conf.node("gameMode").getInt();
useBrandName = conf.node("brandName", "enable").getBoolean();
useJoinMessage = conf.node("joinMessage", "enable").getBoolean();
useBossBar = conf.node("bossBar", "enable").getBoolean();
useTitle = conf.node("title", "enable").getBoolean();
usePlayerList = conf.node("playerList", "enable").getBoolean();
playerListUsername = conf.node("playerList", "username").getString();
useHeaderAndFooter = conf.node("headerAndFooter", "enable").getBoolean();
if (useBrandName)
brandName = conf.node("brandName", "content").getString();
if (useJoinMessage)
joinMessage = Colors.of(conf.node("joinMessage", "text").getString(""));
if (useBossBar)
bossBar = conf.node("bossBar").get(BossBar.class);
if (useTitle)
title = conf.node("title").get(Title.class);
if (useHeaderAndFooter) {
playerListHeader = Colors.of(conf.node("headerAndFooter", "header").getString());
playerListFooter = Colors.of(conf.node("headerAndFooter", "footer").getString());
}
infoForwarding = conf.node("infoForwarding").get(InfoForwarding.class);
readTimeout = conf.node("readTimeout").getLong();
debugLevel = conf.node("debugLevel").getInt();
useEpoll = conf.node("netty", "useEpoll").getBoolean(true);
bossGroupSize = conf.node("netty", "threads", "bossGroup").getInt(1);
workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4);
useTrafficLimits = conf.node("traffic", "enable").getBoolean(false);
maxPacketSize = conf.node("traffic", "maxPacketSize").getInt(-1);
interval = conf.node("traffic", "interval").getDouble(-1.0);
maxPacketRate = conf.node("traffic", "maxPacketRate").getDouble(-1.0);
}
private BufferedReader getReader() throws IOException {
String name = "settings.yml";
Path filePath = Paths.get(root.toString(), name);
if (!Files.exists(filePath)) {
InputStream stream = getClass().getResourceAsStream( "/" + name);
if (stream == null)
throw new FileNotFoundException("Cannot find settings resource file");
Files.copy(stream, filePath);
}
return Files.newBufferedReader(filePath);
}
private TypeSerializerCollection getSerializers() {
return TypeSerializerCollection.builder()
.register(SocketAddress.class, new SocketAddressSerializer())
.register(InfoForwarding.class, new InfoForwarding.Serializer())
.register(PingData.class, new PingData.Serializer())
.register(BossBar.class, new BossBar.Serializer())
.register(Title.class, new Title.Serializer())
.build();
}
public SocketAddress getAddress() {
return address;
}
public int getMaxPlayers() {
return maxPlayers;
}
public PingData getPingData() {
return pingData;
}
public String getDimensionType() {
return dimensionType;
}
public int getGameMode() {
return gameMode;
}
public InfoForwarding getInfoForwarding() {
return infoForwarding;
}
public long getReadTimeout() {
return readTimeout;
}
public int getDebugLevel() {
return debugLevel;
}
public boolean isUseBrandName() {
return useBrandName;
}
public boolean isUseJoinMessage() {
return useJoinMessage;
}
public boolean isUseBossBar() {
return useBossBar;
}
public boolean isUseTitle() {
return useTitle;
}
public boolean isUsePlayerList() {
return usePlayerList;
}
public boolean isUseHeaderAndFooter() {
return useHeaderAndFooter;
}
public String getBrandName() {
return brandName;
}
public String getJoinMessage() {
return joinMessage;
}
public BossBar getBossBar() {
return bossBar;
}
public Title getTitle() {
return title;
}
public String getPlayerListUsername() {
return playerListUsername;
}
public String getPlayerListHeader() {
return playerListHeader;
}
public String getPlayerListFooter() {
return playerListFooter;
}
public boolean isUseEpoll() {
return useEpoll;
}
public int getBossGroupSize() {
return bossGroupSize;
}
public int getWorkerGroupSize() {
return workerGroupSize;
}
public boolean isUseTrafficLimits() {
return useTrafficLimits;
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public double getInterval() {
return interval;
}
public double getMaxPacketRate() {
return maxPacketRate;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.configuration;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class SocketAddressSerializer implements TypeSerializer<SocketAddress> {
@Override
public SocketAddress deserialize(Type type, ConfigurationNode node) {
String ip = node.node("ip").getString();
int port = node.node("port").getInt();
SocketAddress address;
if (ip == null || ip.isEmpty()) {
address = new InetSocketAddress(port);
} else {
address = new InetSocketAddress(ip, port);
}
return address;
}
@Override
public void serialize(Type type, @Nullable SocketAddress obj, ConfigurationNode node) {
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.ReadTimeoutHandler;
import ua.nanit.limbo.connection.pipeline.*;
import ua.nanit.limbo.server.LimboServer;
import java.util.concurrent.TimeUnit;
public class ClientChannelInitializer extends ChannelInitializer<Channel> {
private final LimboServer server;
public ClientChannelInitializer(LimboServer server) {
this.server = server;
}
@Override
protected void initChannel(Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
PacketDecoder decoder = new PacketDecoder();
PacketEncoder encoder = new PacketEncoder();
ClientConnection connection = new ClientConnection(channel, server, decoder, encoder);
pipeline.addLast("timeout", new ReadTimeoutHandler(server.getConfig().getReadTimeout(),
TimeUnit.MILLISECONDS));
pipeline.addLast("frame_decoder", new VarIntFrameDecoder());
pipeline.addLast("frame_encoder", new VarIntLengthEncoder());
if (server.getConfig().isUseTrafficLimits()) {
pipeline.addLast("traffic_limit", new ChannelTrafficHandler(
server.getConfig().getMaxPacketSize(),
server.getConfig().getInterval(),
server.getConfig().getMaxPacketRate()
));
}
pipeline.addLast("decoder", decoder);
pipeline.addLast("encoder", encoder);
pipeline.addLast("handler", connection);
}
}

View File

@ -0,0 +1,347 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull;
import ua.nanit.limbo.connection.pipeline.PacketDecoder;
import ua.nanit.limbo.connection.pipeline.PacketEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.packets.login.PacketDisconnect;
import ua.nanit.limbo.protocol.packets.play.PacketKeepAlive;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log;
import ua.nanit.limbo.util.UuidUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class ClientConnection extends ChannelInboundHandlerAdapter {
private final LimboServer server;
private final Channel channel;
private final GameProfile gameProfile;
private final PacketDecoder decoder;
private final PacketEncoder encoder;
private State state;
private Version clientVersion;
private SocketAddress address;
private int velocityLoginMessageId = -1;
public ClientConnection(Channel channel, LimboServer server, PacketDecoder decoder, PacketEncoder encoder) {
this.server = server;
this.channel = channel;
this.decoder = decoder;
this.encoder = encoder;
this.address = channel.remoteAddress();
this.gameProfile = new GameProfile();
}
public UUID getUuid() {
return gameProfile.getUuid();
}
public String getUsername() {
return gameProfile.getUsername();
}
public SocketAddress getAddress() {
return address;
}
public Version getClientVersion() {
return clientVersion;
}
public GameProfile getGameProfile() {
return gameProfile;
}
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY) || state.equals(State.CONFIGURATION)) {
server.getConnections().removeConnection(this);
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()) {
Log.error("Unhandled exception: ", cause);
}
}
@Override
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) {
handlePacket(msg);
}
public void handlePacket(Object packet) {
if (packet instanceof Packet) {
((Packet) packet).handle(this, server);
}
}
public void fireLoginSuccess() {
if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1) {
disconnectLogin("You need to connect with Velocity");
return;
}
sendPacket(PacketSnapshots.PACKET_LOGIN_SUCCESS);
server.getConnections().addConnection(this);
// Preparing for configuration mode
if (clientVersion.moreOrEqual(Version.V1_20_2)) {
updateEncoderState(State.CONFIGURATION);
return;
}
spawnPlayer();
}
public void spawnPlayer() {
updateState(State.PLAY);
Runnable sendPlayPackets = () -> {
writePacket(PacketSnapshots.PACKET_JOIN_GAME);
writePacket(PacketSnapshots.PACKET_PLAYER_ABILITIES);
if (clientVersion.less(Version.V1_9)) {
writePacket(PacketSnapshots.PACKET_PLAYER_POS_AND_LOOK_LEGACY);
} else {
writePacket(PacketSnapshots.PACKET_PLAYER_POS_AND_LOOK);
}
if (clientVersion.moreOrEqual(Version.V1_19_3))
writePacket(PacketSnapshots.PACKET_SPAWN_POSITION);
if (server.getConfig().isUsePlayerList() || clientVersion.equals(Version.V1_16_4))
writePacket(PacketSnapshots.PACKET_PLAYER_INFO);
if (clientVersion.moreOrEqual(Version.V1_13)) {
writePacket(PacketSnapshots.PACKET_DECLARE_COMMANDS);
if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE);
}
if (PacketSnapshots.PACKET_BOSS_BAR != null && clientVersion.moreOrEqual(Version.V1_9))
writePacket(PacketSnapshots.PACKET_BOSS_BAR);
if (PacketSnapshots.PACKET_JOIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_JOIN_MESSAGE);
if (PacketSnapshots.PACKET_TITLE_TITLE != null && clientVersion.moreOrEqual(Version.V1_8))
writeTitle();
if (PacketSnapshots.PACKET_HEADER_AND_FOOTER != null && clientVersion.moreOrEqual(Version.V1_8))
writePacket(PacketSnapshots.PACKET_HEADER_AND_FOOTER);
if (clientVersion.moreOrEqual(Version.V1_20_3)) {
writePacket(PacketSnapshots.PACKET_START_WAITING_CHUNKS);
for (PacketSnapshot chunk : PacketSnapshots.PACKETS_EMPTY_CHUNKS) {
writePacket(chunk);
}
}
sendKeepAlive();
};
if (clientVersion.lessOrEqual(Version.V1_7_6)) {
this.channel.eventLoop().schedule(sendPlayPackets, 100, TimeUnit.MILLISECONDS);
} else {
sendPlayPackets.run();
}
}
public void onLoginAcknowledgedReceived() {
updateState(State.CONFIGURATION);
if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE);
if (clientVersion.moreOrEqual(Version.V1_20_5)) {
for (PacketSnapshot packet : PacketSnapshots.PACKETS_REGISTRY_DATA) {
writePacket(packet);
}
} else {
writePacket(PacketSnapshots.PACKET_REGISTRY_DATA);
}
sendPacket(PacketSnapshots.PACKET_FINISH_CONFIGURATION);
}
public void disconnectLogin(String reason) {
if (isConnected() && state == State.LOGIN) {
PacketDisconnect disconnect = new PacketDisconnect();
disconnect.setReason(reason);
sendPacketAndClose(disconnect);
}
}
public void writeTitle() {
if (clientVersion.moreOrEqual(Version.V1_17)) {
writePacket(PacketSnapshots.PACKET_TITLE_TITLE);
writePacket(PacketSnapshots.PACKET_TITLE_SUBTITLE);
writePacket(PacketSnapshots.PACKET_TITLE_TIMES);
} else {
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_TITLE);
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_SUBTITLE);
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_TIMES);
}
}
public void sendKeepAlive() {
if (state.equals(State.PLAY)) {
PacketKeepAlive keepAlive = new PacketKeepAlive();
keepAlive.setId(ThreadLocalRandom.current().nextLong());
sendPacket(keepAlive);
}
}
public void sendPacket(Object packet) {
if (isConnected())
channel.writeAndFlush(packet, channel.voidPromise());
}
public void sendPacketAndClose(Object packet) {
if (isConnected())
channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
public void writePacket(Object packet) {
if (isConnected())
channel.write(packet, channel.voidPromise());
}
public boolean isConnected() {
return channel.isActive();
}
public void updateState(State state) {
this.state = state;
decoder.updateState(state);
encoder.updateState(state);
}
public void updateEncoderState(State state) {
encoder.updateState(state);
}
public void updateVersion(Version version) {
clientVersion = version;
decoder.updateVersion(version);
encoder.updateVersion(version);
}
public void setAddress(String host) {
this.address = new InetSocketAddress(host, ((InetSocketAddress) this.address).getPort());
}
boolean checkBungeeGuardHandshake(String handshake) {
String[] split = handshake.split("\00");
if (split.length != 4)
return false;
String socketAddressHostname = split[1];
UUID uuid = UuidUtil.fromString(split[2]);
JsonArray arr;
try {
arr = JsonParser.array().from(split[3]);
} catch (JsonParserException e) {
return false;
}
String token = null;
for (Object obj : arr) {
if (obj instanceof JsonObject) {
JsonObject prop = (JsonObject) obj;
if (prop.getString("name").equals("bungeeguard-token")) {
token = prop.getString("value");
break;
}
}
}
if (!server.getConfig().getInfoForwarding().hasToken(token))
return false;
setAddress(socketAddressHostname);
gameProfile.setUuid(uuid);
Log.debug("Successfully verified BungeeGuard token");
return true;
}
int getVelocityLoginMessageId() {
return velocityLoginMessageId;
}
void setVelocityLoginMessageId(int velocityLoginMessageId) {
this.velocityLoginMessageId = velocityLoginMessageId;
}
boolean checkVelocityKeyIntegrity(ByteMessage buf) {
byte[] signature = new byte[32];
buf.readBytes(signature);
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(server.getConfig().getInfoForwarding().getSecretKey(), "HmacSHA256"));
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException | java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();
if (version != 1)
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001');
return true;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import java.util.UUID;
public class GameProfile {
private UUID uuid;
private String username;
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import io.netty.buffer.Unpooled;
import ua.nanit.limbo.LimboConstants;
import ua.nanit.limbo.protocol.packets.PacketHandshake;
import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration;
import ua.nanit.limbo.protocol.packets.login.PacketLoginAcknowledged;
import ua.nanit.limbo.protocol.packets.login.PacketLoginPluginRequest;
import ua.nanit.limbo.protocol.packets.login.PacketLoginPluginResponse;
import ua.nanit.limbo.protocol.packets.login.PacketLoginStart;
import ua.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ua.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ua.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log;
import ua.nanit.limbo.util.UuidUtil;
import java.util.concurrent.ThreadLocalRandom;
public class PacketHandler {
private final LimboServer server;
public PacketHandler(LimboServer server) {
this.server = server;
}
public void handle(ClientConnection conn, PacketHandshake packet) {
conn.updateVersion(packet.getVersion());
conn.updateState(packet.getNextState());
Log.debug("Pinged from %s [%s]", conn.getAddress(),
conn.getClientVersion().toString());
if (server.getConfig().getInfoForwarding().isLegacy()) {
String[] split = packet.getHost().split("\00");
if (split.length == 3 || split.length == 4) {
conn.setAddress(split[1]);
conn.getGameProfile().setUuid(UuidUtil.fromString(split[2]));
} else {
conn.disconnectLogin("You've enabled player info forwarding. You need to connect with proxy");
}
} else if (server.getConfig().getInfoForwarding().isBungeeGuard()) {
if (!conn.checkBungeeGuardHandshake(packet.getHost())) {
conn.disconnectLogin("Invalid BungeeGuard token or handshake format");
}
}
}
public void handle(ClientConnection conn, PacketStatusRequest packet) {
conn.sendPacket(new PacketStatusResponse(server));
}
public void handle(ClientConnection conn, PacketStatusPing packet) {
conn.sendPacketAndClose(packet);
}
public void handle(ClientConnection conn, PacketLoginStart packet) {
if (server.getConfig().getMaxPlayers() > 0 &&
server.getConnections().getCount() >= server.getConfig().getMaxPlayers()) {
conn.disconnectLogin("Too many players connected");
return;
}
if (!conn.getClientVersion().isSupported()) {
conn.disconnectLogin("Unsupported client version");
return;
}
if (server.getConfig().getInfoForwarding().isModern()) {
int loginId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
PacketLoginPluginRequest request = new PacketLoginPluginRequest();
request.setMessageId(loginId);
request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL);
request.setData(Unpooled.EMPTY_BUFFER);
conn.setVelocityLoginMessageId(loginId);
conn.sendPacket(request);
return;
}
if (!server.getConfig().getInfoForwarding().isModern()) {
conn.getGameProfile().setUsername(packet.getUsername());
conn.getGameProfile().setUuid(UuidUtil.getOfflineModeUuid(packet.getUsername()));
}
conn.fireLoginSuccess();
}
public void handle(ClientConnection conn, PacketLoginPluginResponse packet) {
if (server.getConfig().getInfoForwarding().isModern()
&& packet.getMessageId() == conn.getVelocityLoginMessageId()) {
if (!packet.isSuccessful() || packet.getData() == null) {
conn.disconnectLogin("You need to connect with Velocity");
return;
}
if (!conn.checkVelocityKeyIntegrity(packet.getData())) {
conn.disconnectLogin("Can't verify forwarded player info");
return;
}
// Order is important
conn.setAddress(packet.getData().readString());
conn.getGameProfile().setUuid(packet.getData().readUuid());
conn.getGameProfile().setUsername(packet.getData().readString());
conn.fireLoginSuccess();
}
}
public void handle(ClientConnection conn, PacketLoginAcknowledged packet) {
conn.onLoginAcknowledgedReceived();
}
public void handle(ClientConnection conn, PacketFinishConfiguration packet) {
conn.spawnPlayer();
}
}

View File

@ -0,0 +1,257 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import ua.nanit.limbo.LimboConstants;
import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration;
import ua.nanit.limbo.protocol.packets.configuration.PacketRegistryData;
import ua.nanit.limbo.protocol.packets.login.PacketLoginSuccess;
import ua.nanit.limbo.protocol.packets.play.*;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.data.Title;
import ua.nanit.limbo.util.NbtMessageUtil;
import ua.nanit.limbo.util.UuidUtil;
import ua.nanit.limbo.world.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
public final class PacketSnapshots {
public static PacketSnapshot PACKET_LOGIN_SUCCESS;
public static PacketSnapshot PACKET_JOIN_GAME;
public static PacketSnapshot PACKET_SPAWN_POSITION;
public static PacketSnapshot PACKET_PLUGIN_MESSAGE;
public static PacketSnapshot PACKET_PLAYER_ABILITIES;
public static PacketSnapshot PACKET_PLAYER_INFO;
public static PacketSnapshot PACKET_DECLARE_COMMANDS;
public static PacketSnapshot PACKET_JOIN_MESSAGE;
public static PacketSnapshot PACKET_BOSS_BAR;
public static PacketSnapshot PACKET_HEADER_AND_FOOTER;
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK_LEGACY;
// For 1.19 we need to spawn player outside the world to avoid stuck in terrain loading
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK;
public static PacketSnapshot PACKET_TITLE_TITLE;
public static PacketSnapshot PACKET_TITLE_SUBTITLE;
public static PacketSnapshot PACKET_TITLE_TIMES;
public static PacketSnapshot PACKET_TITLE_LEGACY_TITLE;
public static PacketSnapshot PACKET_TITLE_LEGACY_SUBTITLE;
public static PacketSnapshot PACKET_TITLE_LEGACY_TIMES;
public static PacketSnapshot PACKET_REGISTRY_DATA;
public static List<PacketSnapshot> PACKETS_REGISTRY_DATA;
public static PacketSnapshot PACKET_FINISH_CONFIGURATION;
public static List<PacketSnapshot> PACKETS_EMPTY_CHUNKS;
public static PacketSnapshot PACKET_START_WAITING_CHUNKS;
private PacketSnapshots() { }
public static void initPackets(LimboServer server) {
final String username = server.getConfig().getPingData().getVersion();
final UUID uuid = UuidUtil.getOfflineModeUuid(username);
PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUsername(username);
loginSuccess.setUuid(uuid);
PacketJoinGame joinGame = new PacketJoinGame();
String worldName = "minecraft:" + server.getConfig().getDimensionType().toLowerCase();
joinGame.setEntityId(0);
joinGame.setEnableRespawnScreen(true);
joinGame.setFlat(false);
joinGame.setGameMode(server.getConfig().getGameMode());
joinGame.setHardcore(false);
joinGame.setMaxPlayers(server.getConfig().getMaxPlayers());
joinGame.setPreviousGameMode(-1);
joinGame.setReducedDebugInfo(true);
joinGame.setDebug(false);
joinGame.setViewDistance(0);
joinGame.setWorldName(worldName);
joinGame.setWorldNames(worldName);
joinGame.setHashedSeed(0);
joinGame.setDimensionRegistry(server.getDimensionRegistry());
PacketPlayerAbilities playerAbilities = new PacketPlayerAbilities();
playerAbilities.setFlyingSpeed(0.0F);
playerAbilities.setFlags(0x02);
playerAbilities.setFieldOfView(0.1F);
int teleportId = ThreadLocalRandom.current().nextInt();
PacketPlayerPositionAndLook positionAndLookLegacy
= new PacketPlayerPositionAndLook(0, 64, 0, 0, 0, teleportId);
PacketPlayerPositionAndLook positionAndLook
= new PacketPlayerPositionAndLook(0, 400, 0, 0, 0, teleportId);
PacketSpawnPosition packetSpawnPosition = new PacketSpawnPosition(0, 400, 0);
PacketDeclareCommands declareCommands = new PacketDeclareCommands();
declareCommands.setCommands(Collections.emptyList());
PacketPlayerInfo info = new PacketPlayerInfo();
info.setUsername(server.getConfig().getPlayerListUsername());
info.setGameMode(server.getConfig().getGameMode());
info.setUuid(uuid);
PACKET_LOGIN_SUCCESS = PacketSnapshot.of(loginSuccess);
PACKET_JOIN_GAME = PacketSnapshot.of(joinGame);
PACKET_PLAYER_POS_AND_LOOK_LEGACY = PacketSnapshot.of(positionAndLookLegacy);
PACKET_PLAYER_POS_AND_LOOK = PacketSnapshot.of(positionAndLook);
PACKET_SPAWN_POSITION = PacketSnapshot.of(packetSpawnPosition);
PACKET_PLAYER_ABILITIES = PacketSnapshot.of(playerAbilities);
PACKET_PLAYER_INFO = PacketSnapshot.of(info);
PACKET_DECLARE_COMMANDS = PacketSnapshot.of(declareCommands);
if (server.getConfig().isUseHeaderAndFooter()) {
PacketPlayerListHeader header = new PacketPlayerListHeader();
header.setHeader(NbtMessageUtil.create(server.getConfig().getPlayerListHeader()));
header.setFooter(NbtMessageUtil.create(server.getConfig().getPlayerListFooter()));
PACKET_HEADER_AND_FOOTER = PacketSnapshot.of(header);
}
if (server.getConfig().isUseBrandName()){
PacketPluginMessage pluginMessage = new PacketPluginMessage();
pluginMessage.setChannel(LimboConstants.BRAND_CHANNEL);
pluginMessage.setMessage(server.getConfig().getBrandName());
PACKET_PLUGIN_MESSAGE = PacketSnapshot.of(pluginMessage);
}
if (server.getConfig().isUseJoinMessage()) {
PacketChatMessage joinMessage = new PacketChatMessage();
joinMessage.setMessage(NbtMessageUtil.create(server.getConfig().getJoinMessage()));
joinMessage.setPosition(PacketChatMessage.PositionLegacy.SYSTEM_MESSAGE);
joinMessage.setSender(UUID.randomUUID());
PACKET_JOIN_MESSAGE = PacketSnapshot.of(joinMessage);
}
if (server.getConfig().isUseBossBar()) {
PacketBossBar bossBar = new PacketBossBar();
bossBar.setBossBar(server.getConfig().getBossBar());
bossBar.setUuid(UUID.randomUUID());
PACKET_BOSS_BAR = PacketSnapshot.of(bossBar);
}
if (server.getConfig().isUseTitle()) {
Title title = server.getConfig().getTitle();
PacketTitleSetTitle packetTitle = new PacketTitleSetTitle();
PacketTitleSetSubTitle packetSubtitle = new PacketTitleSetSubTitle();
PacketTitleTimes packetTimes = new PacketTitleTimes();
PacketTitleLegacy legacyTitle = new PacketTitleLegacy();
PacketTitleLegacy legacySubtitle = new PacketTitleLegacy();
PacketTitleLegacy legacyTimes = new PacketTitleLegacy();
packetTitle.setTitle(title.getTitle());
packetSubtitle.setSubtitle(title.getSubtitle());
packetTimes.setFadeIn(title.getFadeIn());
packetTimes.setStay(title.getStay());
packetTimes.setFadeOut(title.getFadeOut());
legacyTitle.setTitle(title);
legacyTitle.setAction(PacketTitleLegacy.Action.SET_TITLE);
legacySubtitle.setTitle(title);
legacySubtitle.setAction(PacketTitleLegacy.Action.SET_SUBTITLE);
legacyTimes.setTitle(title);
legacyTimes.setAction(PacketTitleLegacy.Action.SET_TIMES_AND_DISPLAY);
PACKET_TITLE_TITLE = PacketSnapshot.of(packetTitle);
PACKET_TITLE_SUBTITLE = PacketSnapshot.of(packetSubtitle);
PACKET_TITLE_TIMES = PacketSnapshot.of(packetTimes);
PACKET_TITLE_LEGACY_TITLE = PacketSnapshot.of(legacyTitle);
PACKET_TITLE_LEGACY_SUBTITLE = PacketSnapshot.of(legacySubtitle);
PACKET_TITLE_LEGACY_TIMES = PacketSnapshot.of(legacyTimes);
}
PacketRegistryData packetRegistryData = new PacketRegistryData();
packetRegistryData.setDimensionRegistry(server.getDimensionRegistry());
PACKET_REGISTRY_DATA = PacketSnapshot.of(packetRegistryData);
Dimension dimension1_21 = server.getDimensionRegistry().getDimension_1_21();
List<PacketSnapshot> packetRegistries = new ArrayList<>();
CompoundBinaryTag dimensionTag = dimension1_21.getData();
for (String registryType : dimensionTag.keySet()) {
CompoundBinaryTag compoundRegistryType = dimensionTag.getCompound(registryType);
PacketRegistryData registryData = new PacketRegistryData();
registryData.setDimensionRegistry(server.getDimensionRegistry());
ListBinaryTag values = compoundRegistryType.getList("value");
registryData.setMetadataWriter((message, version) -> {
message.writeString(registryType);
message.writeVarInt(values.size());
for (BinaryTag entry : values) {
CompoundBinaryTag entryTag = (CompoundBinaryTag) entry;
String name = entryTag.getString("name");
CompoundBinaryTag element = entryTag.getCompound("element");
message.writeString(name);
message.writeBoolean(true);
message.writeNamelessCompoundTag(element);
}
});
packetRegistries.add(PacketSnapshot.of(registryData));
}
PACKETS_REGISTRY_DATA = packetRegistries;
PACKET_FINISH_CONFIGURATION = PacketSnapshot.of(new PacketFinishConfiguration());
PacketGameEvent packetGameEvent = new PacketGameEvent();
packetGameEvent.setType((byte) 13); // Waiting for chunks type
packetGameEvent.setValue(0);
PACKET_START_WAITING_CHUNKS = PacketSnapshot.of(packetGameEvent);
int chunkXOffset = (int) 0 >> 4; // Default x position is 0
int chunkZOffset = (int) 0 >> 4; // Default z position is 0
int chunkEdgeSize = 1; // TODO Make configurable?
List<PacketSnapshot> emptyChunks = new ArrayList<>();
// Make multiple chunks for edges
for (int chunkX = chunkXOffset - chunkEdgeSize; chunkX <= chunkXOffset + chunkEdgeSize; ++chunkX) {
for (int chunkZ = chunkZOffset - chunkEdgeSize; chunkZ <= chunkZOffset + chunkEdgeSize; ++chunkZ) {
PacketEmptyChunk packetEmptyChunk = new PacketEmptyChunk();
packetEmptyChunk.setX(chunkX);
packetEmptyChunk.setZ(chunkZ);
emptyChunks.add(PacketSnapshot.of(packetEmptyChunk));
}
}
PACKETS_EMPTY_CHUNKS = emptyChunks;
}
}

View File

@ -0,0 +1,111 @@
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull;
import ua.nanit.limbo.server.Log;
import java.util.Arrays;
public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
private final int maxPacketSize;
private final double maxPacketRate;
private final PacketBucket packetBucket;
public ChannelTrafficHandler(int maxPacketSize, double interval, double maxPacketRate) {
this.maxPacketSize = maxPacketSize;
this.maxPacketRate = maxPacketRate;
this.packetBucket = (interval > 0.0 && maxPacketRate > 0.0) ? new PacketBucket(interval * 1000.0, 150) : null;
}
@Override
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf in = (ByteBuf) msg;
int bytes = in.readableBytes();
if (maxPacketSize > 0 && bytes > maxPacketSize) {
closeConnection(ctx, "Closed %s due to large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes);
return;
}
if (packetBucket != null) {
packetBucket.incrementPackets(1);
if (packetBucket.getCurrentPacketRate() > maxPacketRate) {
closeConnection(ctx, "Closed %s due to many packets sent (%d in the last %.1f seconds)", ctx.channel().remoteAddress(), packetBucket.sum, (packetBucket.intervalTime / 1000.0));
return;
}
}
}
super.channelRead(ctx, msg);
}
private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) {
ctx.close();
Log.info(reason, args);
}
private static class PacketBucket {
private static final double NANOSECONDS_TO_MILLISECONDS = 1.0e-6;
private static final int MILLISECONDS_TO_SECONDS = 1000;
private final double intervalTime;
private final double intervalResolution;
private final int[] data;
private int newestData;
private double lastBucketTime;
private int sum;
public PacketBucket(final double intervalTime, final int totalBuckets) {
this.intervalTime = intervalTime;
this.intervalResolution = intervalTime / totalBuckets;
this.data = new int[totalBuckets];
}
public void incrementPackets(final int packets) {
double timeMs = System.nanoTime() * NANOSECONDS_TO_MILLISECONDS;
double timeDelta = timeMs - this.lastBucketTime;
if (timeDelta < 0.0) {
timeDelta = 0.0;
}
if (timeDelta < this.intervalResolution) {
this.data[this.newestData] += packets;
this.sum += packets;
return;
}
int bucketsToMove = (int)(timeDelta / this.intervalResolution);
double nextBucketTime = this.lastBucketTime + bucketsToMove * this.intervalResolution;
if (bucketsToMove >= this.data.length) {
Arrays.fill(this.data, 0);
this.data[0] = packets;
this.sum = packets;
this.newestData = 0;
this.lastBucketTime = timeMs;
return;
}
for (int i = 1; i < bucketsToMove; ++i) {
int index = (this.newestData + i) % this.data.length;
this.sum -= this.data[index];
this.data[index] = 0;
}
int newestDataIndex = (this.newestData + bucketsToMove) % this.data.length;
this.sum += packets - this.data[newestDataIndex];
this.data[newestDataIndex] = packets;
this.newestData = newestDataIndex;
this.lastBucketTime = nextBucketTime;
}
public double getCurrentPacketRate() {
return this.sum / (this.intervalTime / MILLISECONDS_TO_SECONDS);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Log;
import java.util.List;
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private State.PacketRegistry mappings;
private Version version;
public PacketDecoder() {
updateVersion(Version.getMin());
updateState(State.HANDSHAKING);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if (!ctx.channel().isActive() || mappings == null) return;
ByteMessage msg = new ByteMessage(buf);
int packetId = msg.readVarInt();
Packet packet = mappings.getPacket(packetId);
if (packet != null) {
Log.debug("Received packet %s[0x%s] (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
try {
packet.decode(msg, version);
} catch (Exception e) {
if (Log.isDebug()) {
Log.warning("Cannot decode packet 0x%s", e, Integer.toHexString(packetId));
} else {
Log.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
ctx.fireChannelRead(packet);
} else {
Log.debug("Undefined incoming packet: 0x" + Integer.toHexString(packetId));
}
}
public void updateVersion(Version version) {
this.version = version;
}
public void updateState(State state) {
this.mappings = state.serverBound.getRegistry(version);
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Log;
public class PacketEncoder extends MessageToByteEncoder<Packet> {
private State.PacketRegistry registry;
private Version version;
public PacketEncoder() {
updateVersion(Version.getMin());
updateState(State.HANDSHAKING);
}
@Override
protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception {
if (registry == null) return;
ByteMessage msg = new ByteMessage(out);
int packetId;
if (packet instanceof PacketSnapshot) {
packetId = registry.getPacketId(((PacketSnapshot)packet).getWrappedPacket().getClass());
} else {
packetId = registry.getPacketId(packet.getClass());
}
if (packetId == -1) {
Log.warning("Undefined packet class: %s[0x%s] (%d bytes)", packet.getClass().getName(), Integer.toHexString(packetId), msg.readableBytes());
return;
}
msg.writeVarInt(packetId);
try {
packet.encode(msg, version);
if (Log.isDebug()) {
Log.debug("Sending %s[0x%s] packet (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
}
} catch (Exception e) {
Log.error("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
public void updateVersion(Version version) {
this.version = version;
}
public void updateState(State state) {
this.registry = state.clientBound.getRegistry(version);
}
}

View File

@ -1,4 +1,21 @@
package ru.nanit.limbo.connection.pipeline;
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.util.ByteProcessor;

View File

@ -1,9 +1,26 @@
package ru.nanit.limbo.connection.pipeline;
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import ru.nanit.limbo.util.Logger;
import ua.nanit.limbo.server.Log;
import java.util.List;
@ -25,18 +42,19 @@ public class VarIntFrameDecoder extends ByteToMessageDecoder {
int readVarInt = reader.getReadVarInt();
int bytesRead = reader.getBytesRead();
if (readVarInt < 0) {
Logger.error("[VarIntFrameDecoder] Bad data length");
Log.error("[VarIntFrameDecoder] Bad data length");
} else if (readVarInt == 0) {
in.readerIndex(varIntEnd + 1);
} else {
int minimumRead = bytesRead + readVarInt;
if (in.isReadable(minimumRead)) {
out.add(in.retainedSlice(varIntEnd + 1, readVarInt));
in.skipBytes(minimumRead);
}
}
} else if (reader.getResult() == VarIntByteDecoder.DecodeResult.TOO_BIG) {
Logger.error("[VarIntFrameDecoder] Too big data");
Log.error("[VarIntFrameDecoder] Too big data");
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
@ChannelHandler.Sharable
public class VarIntLengthEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) {
ByteMessage msg = new ByteMessage(out);
msg.writeVarInt(buf.readableBytes());
msg.writeBytes(buf);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) {
int anticipatedRequiredCapacity = 5 + msg.readableBytes();
return ctx.alloc().heapBuffer(anticipatedRequiredCapacity);
}
}

View File

@ -1,11 +1,28 @@
package ru.nanit.limbo.protocol;
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import io.netty.buffer.*;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import io.netty.util.ByteProcessor;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.*;
import ua.nanit.limbo.protocol.registry.Version;
import java.io.IOException;
import java.io.InputStream;
@ -17,23 +34,26 @@ import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.UUID;
public class ByteMessage extends ByteBuf {
private final ByteBuf buf;
public ByteMessage(ByteBuf buf){
public ByteMessage(ByteBuf buf) {
this.buf = buf;
}
public byte[] toByteArray(){
public byte[] toByteArray() {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
return bytes;
}
/* Minecraft protocol methods */
/* Minecraft's protocol methods */
public int readVarInt() {
int i = 0;
@ -47,18 +67,43 @@ public class ByteMessage extends ByteBuf {
}
}
buf.readBytes(maxRead);
throw new IllegalArgumentException("Cannot read VarInt");
}
public void writeVarInt(int value){
while (true) {
if ((value & 0xFFFFFF80) == 0) {
public void writeVarInt(int value) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
// that the proxy will write, to improve inlining.
if ((value & (0xFFFFFFFF << 7)) == 0) {
buf.writeByte(value);
return;
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else {
writeVarIntFull(value);
}
}
buf.writeByte(value & 0x7F | 0x80);
value >>>= 7;
private void writeVarIntFull(final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
if ((value & (0xFFFFFFFF << 7)) == 0) {
buf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
buf.writeInt(w);
} else {
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
buf.writeInt(w);
buf.writeByte(value >>> 28);
}
}
@ -78,7 +123,7 @@ public class ByteMessage extends ByteBuf {
buf.writeCharSequence(str, StandardCharsets.UTF_8);
}
public byte[] readBytesArray(){
public byte[] readBytesArray() {
int length = readVarInt();
byte[] array = new byte[length];
buf.readBytes(array);
@ -141,35 +186,111 @@ public class ByteMessage extends ByteBuf {
}
public void writeCompoundTagArray(CompoundBinaryTag[] compoundTags) {
try {
ByteBufOutputStream stream = new ByteBufOutputStream(buf);
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
writeVarInt(compoundTags.length);
for (CompoundBinaryTag tag : compoundTags){
BinaryTagIO.writeDataOutput(tag, stream);
for (CompoundBinaryTag tag : compoundTags) {
BinaryTagIO.writer().write(tag, (OutputStream) stream);
}
} catch (IOException e) {
}
catch (IOException e) {
throw new EncoderException("Cannot write NBT CompoundTag");
}
}
public CompoundBinaryTag readCompoundTag() {
try {
return BinaryTagIO.readDataInput(new ByteBufInputStream(buf));
} catch (IOException thrown) {
try (ByteBufInputStream stream = new ByteBufInputStream(buf)) {
return BinaryTagIO.reader().read((InputStream) stream);
}
catch (IOException thrown) {
throw new DecoderException("Cannot read NBT CompoundTag");
}
}
public void writeCompoundTag(CompoundBinaryTag compoundTag) {
try {
BinaryTagIO.writeDataOutput(compoundTag, new ByteBufOutputStream(buf));
} catch (IOException e) {
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
BinaryTagIO.writer().write(compoundTag, (OutputStream) stream);
}
catch (IOException e) {
throw new EncoderException("Cannot write NBT CompoundTag");
}
}
public void writeNamelessCompoundTag(BinaryTag binaryTag) {
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
stream.writeByte(binaryTag.type().id());
// TODO Find a way to improve this...
if (binaryTag instanceof CompoundBinaryTag) {
CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ByteBinaryTag) {
ByteBinaryTag tag = (ByteBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ShortBinaryTag) {
ShortBinaryTag tag = (ShortBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof IntBinaryTag) {
IntBinaryTag tag = (IntBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof LongBinaryTag) {
LongBinaryTag tag = (LongBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof DoubleBinaryTag) {
DoubleBinaryTag tag = (DoubleBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof StringBinaryTag) {
StringBinaryTag tag = (StringBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ListBinaryTag) {
ListBinaryTag tag = (ListBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof EndBinaryTag) {
EndBinaryTag tag = (EndBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
}
catch (IOException e) {
throw new EncoderException("Cannot write NBT CompoundTag");
}
}
public void writeNbtMessage(NbtMessage nbtMessage, Version version) {
if (version.moreOrEqual(Version.V1_20_3)) {
writeNamelessCompoundTag(nbtMessage.getTag());
}
else {
writeString(nbtMessage.getJson());
}
}
public <E extends Enum<E>> void writeEnumSet(EnumSet<E> enumset, Class<E> oclass) {
E[] enums = oclass.getEnumConstants();
BitSet bits = new BitSet(enums.length);
for (int i = 0; i < enums.length; ++i) {
bits.set(i, enumset.contains(enums[i]));
}
writeFixedBitSet(bits, enums.length, buf);
}
private static void writeFixedBitSet(BitSet bits, int size, ByteBuf buf) {
if (bits.length() > size) {
throw new StackOverflowError("BitSet too large (expected " + size + " got " + bits.size() + ")");
}
buf.writeBytes(Arrays.copyOf(bits.toByteArray(), (size + 8) >> 3));
}
/* Delegated methods */
@Override
@ -1139,7 +1260,7 @@ public class ByteMessage extends ByteBuf {
return buf.release(decrement);
}
public static ByteMessage create(){
public static ByteMessage create() {
return new ByteMessage(Unpooled.buffer());
}
}

View File

@ -0,0 +1,10 @@
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
@FunctionalInterface
public interface MetadataWriter {
void writeData(ByteMessage message, Version version);
}

View File

@ -0,0 +1,30 @@
package ua.nanit.limbo.protocol;
import net.kyori.adventure.nbt.BinaryTag;
public class NbtMessage {
private String json;
private BinaryTag tag;
public NbtMessage(String json, BinaryTag tag) {
this.json = json;
this.tag = tag;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public BinaryTag getTag() {
return tag;
}
public void setTag(BinaryTag tag) {
this.tag = tag;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public interface Packet {
void encode(ByteMessage msg, Version version);
void decode(ByteMessage msg, Version version);
default void handle(ClientConnection conn, LimboServer server) {
// Ignored by default
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
public interface PacketIn extends Packet {
@Override
default void encode(ByteMessage msg, Version version) {
// Can be ignored for incoming packets
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
public interface PacketOut extends Packet {
@Override
default void decode(ByteMessage msg, Version version) {
// Can be ignored for outgoing packets
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.HashMap;
import java.util.Map;
/**
* PacketSnapshot encodes a packet to byte array for each MC version.
* Some versions have the same snapshot, so there are mappings to avoid data copying
*/
public class PacketSnapshot implements PacketOut {
private final PacketOut packet;
private final Map<Version, byte[]> versionMessages = new HashMap<>();
private final Map<Version, Version> mappings = new HashMap<>();
public PacketSnapshot(PacketOut packet) {
this.packet = packet;
}
public PacketOut getWrappedPacket() {
return packet;
}
public void encode() {
Map<Integer, Version> hashes = new HashMap<>();
for (Version version : Version.values()) {
if (version.equals(Version.UNDEFINED)) continue;
ByteMessage encodedMessage = ByteMessage.create();
packet.encode(encodedMessage, version);
int hash = encodedMessage.hashCode();
Version hashed = hashes.get(hash);
if (hashed != null) {
mappings.put(version, hashed);
} else {
hashes.put(hash, version);
mappings.put(version, version);
versionMessages.put(version, encodedMessage.toByteArray());
}
encodedMessage.release();
}
}
@Override
public void encode(ByteMessage msg, Version version) {
Version mapped = mappings.get(version);
byte[] message = versionMessages.get(mapped);
if (message != null)
msg.writeBytes(message);
else
throw new IllegalArgumentException("No mappings for version " + version);
}
@Override
public String toString() {
return packet.getClass().getSimpleName();
}
public static PacketSnapshot of(PacketOut packet) {
PacketSnapshot snapshot = new PacketSnapshot(packet);
snapshot.encode();
return snapshot;
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketHandshake implements PacketIn {
private Version version;
private String host;
private int port;
private State nextState;
public Version getVersion() {
return version;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public State getNextState() {
return nextState;
}
@Override
public void decode(ByteMessage msg, Version version) {
try {
this.version = Version.of(msg.readVarInt());
} catch (IllegalArgumentException e) {
this.version = Version.UNDEFINED;
}
this.host = msg.readString();
this.port = msg.readUnsignedShort();
this.nextState = State.getById(msg.readVarInt());
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
}

View File

@ -0,0 +1,19 @@
package ua.nanit.limbo.protocol.packets.configuration;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.server.LimboServer;
public class PacketFinishConfiguration implements PacketIn, PacketOut {
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,32 @@
package ua.nanit.limbo.protocol.packets.configuration;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.MetadataWriter;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.world.DimensionRegistry;
public class PacketRegistryData implements PacketOut {
private DimensionRegistry dimensionRegistry;
private MetadataWriter metadataWriter;
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public void setMetadataWriter(MetadataWriter metadataWriter) {
this.metadataWriter = metadataWriter;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (metadataWriter != null) {
if (version.moreOrEqual(Version.V1_20_5)) {
metadataWriter.writeData(msg, version);
return;
}
}
msg.writeNamelessCompoundTag(dimensionRegistry.getCodec_1_20());
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketDisconnect implements PacketOut {
private String reason;
public void setReason(String reason) {
this.reason = reason;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(String.format("{\"text\": \"%s\"}", reason));
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,19 @@
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginAcknowledged implements PacketIn, PacketOut {
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketLoginPluginRequest implements PacketOut {
private int messageId;
private String channel;
private ByteBuf data;
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public void setChannel(String channel) {
this.channel = channel;
}
public void setData(ByteBuf data) {
this.data = data;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(messageId);
msg.writeString(channel);
msg.writeBytes(data);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginPluginResponse implements PacketIn {
private int messageId;
private boolean successful;
private ByteMessage data;
public int getMessageId() {
return messageId;
}
public boolean isSuccessful() {
return successful;
}
public ByteMessage getData() {
return data;
}
@Override
public void decode(ByteMessage msg, Version version) {
messageId = msg.readVarInt();
successful = msg.readBoolean();
if (msg.readableBytes() > 0) {
int i = msg.readableBytes();
data = new ByteMessage(msg.readBytes(i));
}
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginStart implements PacketIn {
private String username;
public String getUsername() {
return username;
}
@Override
public void decode(ByteMessage msg, Version version) {
this.username = msg.readString();
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.UUID;
public class PacketLoginSuccess implements PacketOut {
private UUID uuid;
private String username;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_16)) {
msg.writeUuid(uuid);
} else if (version.moreOrEqual(Version.V1_7_6)) {
msg.writeString(uuid.toString());
} else {
msg.writeString(uuid.toString().replace("-", ""));
}
msg.writeString(username);
if (version.moreOrEqual(Version.V1_19)) {
msg.writeVarInt(0);
}
if (version.moreOrEqual(Version.V1_20_5)) {
msg.writeBoolean(true);
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.data.BossBar;
import java.util.UUID;
/**
* Packet for 1.9+
*/
public class PacketBossBar implements PacketOut {
private UUID uuid;
private BossBar bossBar;
private int flags;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setBossBar(BossBar bossBar) {
this.bossBar = bossBar;
}
public void setFlags(int flags) {
this.flags = flags;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeUuid(uuid);
msg.writeVarInt(0); // Create bossbar
msg.writeNbtMessage(bossBar.getText(), version);
msg.writeFloat(bossBar.getHealth());
msg.writeVarInt(bossBar.getColor().getIndex());
msg.writeVarInt(bossBar.getDivision().getIndex());
msg.writeByte(flags);
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.UUID;
public class PacketChatMessage implements PacketOut {
private NbtMessage message;
private PositionLegacy position;
private UUID sender;
public void setMessage(NbtMessage message) {
this.message = message;
}
public void setPosition(PositionLegacy position) {
this.position = position;
}
public void setSender(UUID sender) {
this.sender = sender;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeNbtMessage(message, version);
if (version.moreOrEqual(Version.V1_19_1)) {
msg.writeBoolean(position.index == PositionLegacy.ACTION_BAR.index);
} else if (version.moreOrEqual(Version.V1_19)) {
msg.writeVarInt(position.index);
} else if (version.moreOrEqual(Version.V1_8)) {
msg.writeByte(position.index);
}
if (version.moreOrEqual(Version.V1_16) && version.less(Version.V1_19))
msg.writeUuid(sender);
}
public enum PositionLegacy {
CHAT(0),
SYSTEM_MESSAGE(1),
ACTION_BAR(2);
private final int index;
PositionLegacy(int index) {
this.index = index;
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.List;
/**
* Packet for 1.13+
*/
public class PacketDeclareCommands implements PacketOut {
private List<String> commands;
public void setCommands(List<String> commands) {
this.commands = commands;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(commands.size() * 2 + 1); // +1 because declaring root node
// Declare root node
msg.writeByte(0);
msg.writeVarInt(commands.size());
for (int i = 1; i <= commands.size() * 2; i++) {
msg.writeVarInt(i++);
}
// Declare other commands
int i = 1;
for (String cmd : commands) {
msg.writeByte(1 | 0x04);
msg.writeVarInt(1);
msg.writeVarInt(i + 1);
msg.writeString(cmd);
i++;
msg.writeByte(2 | 0x04 | 0x10);
msg.writeVarInt(1);
msg.writeVarInt(i);
msg.writeString("arg");
msg.writeString("brigadier:string");
msg.writeVarInt(0);
msg.writeString("minecraft:ask_server");
i++;
}
msg.writeVarInt(0);
}
}

View File

@ -0,0 +1,47 @@
package ua.nanit.limbo.protocol.packets.play;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.LongArrayBinaryTag;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketEmptyChunk implements PacketOut {
private int x;
private int z;
public void setX(int x) {
this.x = x;
}
public void setZ(int z) {
this.z = z;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(x);
msg.writeInt(z);
LongArrayBinaryTag longArrayTag = LongArrayBinaryTag.longArrayBinaryTag(new long[37]);
CompoundBinaryTag tag = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", longArrayTag).build();
CompoundBinaryTag rootTag = CompoundBinaryTag.builder()
.put("root", tag).build();
msg.writeNamelessCompoundTag(rootTag);
byte[] sectionData = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
msg.writeVarInt(sectionData.length * 16);
for (int i = 0; i < 16; i++) {
msg.writeBytes(sectionData);
}
msg.writeVarInt(0);
byte[] lightData = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
msg.ensureWritable(lightData.length);
msg.writeBytes(lightData, 1, lightData.length - 1);
}
}

View File

@ -0,0 +1,25 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketGameEvent implements PacketOut {
private byte type;
private float value;
public void setType(byte type) {
this.type = type;
}
public void setValue(float value) {
this.value = value;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeByte(type);
msg.writeFloat(value);
}
}

View File

@ -0,0 +1,305 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.world.DimensionRegistry;
public class PacketJoinGame implements PacketOut {
private int entityId;
private boolean isHardcore = false;
private int gameMode = 2;
private int previousGameMode = -1;
private String[] worldNames;
private DimensionRegistry dimensionRegistry;
private String worldName;
private long hashedSeed;
private int maxPlayers;
private int viewDistance = 2;
private boolean reducedDebugInfo;
private boolean enableRespawnScreen;
private boolean isDebug;
private boolean isFlat;
private boolean limitedCrafting;
private boolean secureProfile;
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public void setHardcore(boolean hardcore) {
isHardcore = hardcore;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setPreviousGameMode(int previousGameMode) {
this.previousGameMode = previousGameMode;
}
public void setWorldNames(String... worldNames) {
this.worldNames = worldNames;
}
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public void setWorldName(String worldName) {
this.worldName = worldName;
}
public void setHashedSeed(long hashedSeed) {
this.hashedSeed = hashedSeed;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
this.enableRespawnScreen = enableRespawnScreen;
}
public void setDebug(boolean debug) {
isDebug = debug;
}
public void setFlat(boolean flat) {
isFlat = flat;
}
public void setLimitedCrafting(boolean limitedCrafting) {
this.limitedCrafting = limitedCrafting;
}
public void setSecureProfile(boolean secureProfile) {
this.secureProfile = secureProfile;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(entityId);
if (version.fromTo(Version.V1_7_2, Version.V1_7_6)) {
msg.writeByte(gameMode == 3 ? 1 : gameMode);
msg.writeByte(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
}
if (version.fromTo(Version.V1_8, Version.V1_9)) {
msg.writeByte(gameMode);
msg.writeByte(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_9_1, Version.V1_13_2)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_14, Version.V1_14_4)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_15, Version.V1_15_2)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeLong(hashedSeed);
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
}
if (version.fromTo(Version.V1_16, Version.V1_16_1)) {
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionRegistry.getOldCodec());
msg.writeString(dimensionRegistry.getDefaultDimension_1_16().getName());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.fromTo(Version.V1_16_2, Version.V1_17_1)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionRegistry.getCodec_1_16());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_16().getData());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.fromTo(Version.V1_18, Version.V1_18_2)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
if (version.moreOrEqual(Version.V1_18_2)) {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_18_2());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_18_2().getData());
} else {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_16());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_16().getData());
}
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.fromTo(Version.V1_19, Version.V1_19_4)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
if (version.moreOrEqual(Version.V1_19_1)) {
if (version.moreOrEqual(Version.V1_19_4)) {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_19_4());
}
else {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_19_1());
}
}
else {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_19());
}
msg.writeString(worldName); // World type
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
}
if (version.equals(Version.V1_20)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionRegistry.getCodec_1_20());
msg.writeString(worldName); // World type
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
msg.writeVarInt(0);
}
if (version.fromTo(Version.V1_20_2, Version.V1_20_3)) {
msg.writeBoolean(isHardcore);
msg.writeStringsArray(worldNames);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(limitedCrafting);
msg.writeString(worldName);
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
msg.writeVarInt(0);
}
if (version.moreOrEqual(Version.V1_20_5)) {
msg.writeBoolean(isHardcore);
msg.writeStringsArray(worldNames);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(limitedCrafting);
msg.writeVarInt(dimensionRegistry.getDimension_1_20_5().getId());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
msg.writeVarInt(0);
msg.writeBoolean(secureProfile);
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketKeepAlive implements Packet {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_12_2)) {
msg.writeLong(id);
} else if (version.moreOrEqual(Version.V1_8)) {
msg.writeVarInt((int) id);
} else {
msg.writeInt((int) id);
}
}
@Override
public void decode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_12_2)) {
this.id = msg.readLong();
} else if (version.moreOrEqual(Version.V1_8)) {
this.id = msg.readVarInt();
} else {
this.id = msg.readInt();
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketPlayerAbilities implements PacketOut {
private int flags = 0x02;
private float flyingSpeed = 0.0F;
private float fieldOfView = 0.1F;
public void setFlags(int flags) {
this.flags = flags;
}
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
}
public void setFieldOfView(float fieldOfView) {
this.fieldOfView = fieldOfView;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeByte(flags);
msg.writeFloat(flyingSpeed);
msg.writeFloat(fieldOfView);
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.EnumSet;
import java.util.UUID;
/**
* This packet was very simplified and using only for ADD_PLAYER action
*/
public class PacketPlayerInfo implements PacketOut {
private int gameMode = 3;
private String username = "";
private UUID uuid;
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setUsername(String username) {
this.username = username;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.less(Version.V1_8)) {
msg.writeString(username);
msg.writeBoolean(true); // Is online
msg.writeShort(0);
} else {
if (version.moreOrEqual(Version.V1_19_3)) {
EnumSet<Action> actions = EnumSet.noneOf(Action.class);
actions.add(Action.ADD_PLAYER);
actions.add(Action.UPDATE_LISTED);
actions.add(Action.UPDATE_GAMEMODE);
msg.writeEnumSet(actions, Action.class);
msg.writeVarInt(1); // Array length (1 element)
msg.writeUuid(uuid); // UUID
msg.writeString(username); //Username
msg.writeVarInt(0); //Properties (0 is empty)
msg.writeBoolean(true); //Update listed
msg.writeVarInt(gameMode); //Gamemode
return;
}
msg.writeVarInt(0); // Add player action
msg.writeVarInt(1);
msg.writeUuid(uuid);
msg.writeString(username);
msg.writeVarInt(0);
msg.writeVarInt(gameMode);
msg.writeVarInt(60);
msg.writeBoolean(false);
if (version.moreOrEqual(Version.V1_19)) {
msg.writeBoolean(false);
}
}
}
public static enum Action {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAMEMODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketPlayerListHeader implements PacketOut {
private NbtMessage header;
private NbtMessage footer;
public void setHeader(NbtMessage header) {
this.header = header;
}
public void setFooter(NbtMessage footer) {
this.footer = footer;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeNbtMessage(header, version);
msg.writeNbtMessage(footer, version);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketPlayerPositionAndLook implements PacketOut {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
private int teleportId;
public PacketPlayerPositionAndLook() {}
public PacketPlayerPositionAndLook(double x, double y, double z, float yaw, float pitch, int teleportId) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.teleportId = teleportId;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeDouble(x);
msg.writeDouble(y + (version.less(Version.V1_8) ? 1.62F : 0));
msg.writeDouble(z);
msg.writeFloat(yaw);
msg.writeFloat(pitch);
if (version.moreOrEqual(Version.V1_8)) {
msg.writeByte(0x08);
} else {
msg.writeBoolean(true);
}
if (version.moreOrEqual(Version.V1_9)) {
msg.writeVarInt(teleportId);
}
if (version.fromTo(Version.V1_17, Version.V1_19_3)) {
msg.writeBoolean(false); // Dismount vehicle
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketPluginMessage implements PacketOut {
private String channel;
private String message;
public void setChannel(String channel) {
this.channel = channel;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(channel);
msg.writeString(message);
}
}

View File

@ -0,0 +1,30 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketSpawnPosition implements PacketOut {
private long x;
private long y;
private long z;
public PacketSpawnPosition() { }
public PacketSpawnPosition(long x, long y, long z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeLong(encodePosition(x, y ,z));
msg.writeFloat(0);
}
private static long encodePosition(long x, long y, long z) {
return ((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF);
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.data.Title;
public class PacketTitleLegacy implements PacketOut {
private Action action;
private final PacketTitleSetTitle title;
private final PacketTitleSetSubTitle subtitle;
private final PacketTitleTimes times;
public PacketTitleLegacy() {
this.title = new PacketTitleSetTitle();
this.subtitle = new PacketTitleSetSubTitle();
this.times = new PacketTitleTimes();
}
public void setAction(Action action) {
this.action = action;
}
public void setTitle(Title title) {
this.title.setTitle(title.getTitle());
this.subtitle.setSubtitle(title.getSubtitle());
this.times.setFadeIn(title.getFadeIn());
this.times.setStay(title.getStay());
this.times.setFadeOut(title.getFadeOut());
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(action.getId(version));
switch (action) {
case SET_TITLE:
title.encode(msg, version);
break;
case SET_SUBTITLE:
subtitle.encode(msg, version);
break;
case SET_TIMES_AND_DISPLAY:
times.encode(msg, version);
break;
default:
throw new IllegalArgumentException("Invalid title action: " + action);
}
}
public enum Action {
SET_TITLE(0),
SET_SUBTITLE(1),
SET_TIMES_AND_DISPLAY(3, 2);
private final int id;
private final int legacyId;
Action(int id, int legacyId) {
this.id = id;
this.legacyId = legacyId;
}
Action(int id) {
this(id, id);
}
public int getId(Version version) {
return version.less(Version.V1_11) ? legacyId : id;
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketTitleSetSubTitle implements PacketOut {
private NbtMessage subtitle;
public void setSubtitle(NbtMessage subtitle) {
this.subtitle = subtitle;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeNbtMessage(subtitle, version);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketTitleSetTitle implements PacketOut {
private NbtMessage title;
public void setTitle(NbtMessage title) {
this.title = title;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeNbtMessage(title, version);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketTitleTimes implements PacketOut {
private int fadeIn;
private int stay;
private int fadeOut;
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
public void setStay(int stay) {
this.stay = stay;
}
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(fadeIn);
msg.writeInt(stay);
msg.writeInt(fadeOut);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.status;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketStatusPing implements Packet {
private long randomId;
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeLong(randomId);
}
@Override
public void decode(ByteMessage msg, Version version) {
this.randomId = msg.readLong();
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.status;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketStatusRequest implements PacketIn {
@Override
public void decode(ByteMessage msg, Version version) {
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

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