Compare commits

...
41 Commits
Author SHA1 Message Date
Jones 0c8106eaf6 Release 2.1.9 (#389) 2024-09-29 13:32:05 +02:00
Jones 92ac5fbeb2 Fix block heights varying on different versions 2024-09-29 13:26:09 +02:00
Jones b800a2d154 Use one entity ID for all vehicles 2024-09-28 12:21:44 +02:00
Jones 1957463b81 Fix the Discord link in Polish translations 2024-09-28 11:59:09 +02:00
Jones 25114eabe5 Add verification welcome message to config 2024-09-28 11:58:54 +02:00
Jones a45b526ffb Clean up and improve the API 2024-09-25 17:52:35 +02:00
Jones 7b70d32e84 Don't store the pipeline separately in user 2024-09-25 17:48:51 +02:00
Jones e84c1a54db Small micro-optimizations to networking 2024-09-25 17:42:51 +02:00
Jones fd869ac199 Fix potential NullPointerException 2024-09-25 17:23:00 +02:00
dependabot[bot] abf1479a84 Bump com.gradleup.shadow from 8.3.1 to 8.3.2 (#387)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.1...8.3.2)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:49 +02:00
dependabot[bot] c00392e062 Bump bstats from 3.0.3 to 3.1.0 (#386)
Bumps `bstats` from 3.0.3 to 3.1.0.

Updates `org.bstats:bstats-bungeecord` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-bukkit` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

Updates `org.bstats:bstats-velocity` from 3.0.3 to 3.1.0
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.3...v3.1.0)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bstats:bstats-velocity
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:03:32 +02:00
Jones fc9c1026b1 Release 2.1.8 (#385) 2024-09-22 13:21:19 +02:00
Jones aae49aeb63 Remove initial "unofficial branch" warning 2024-09-22 13:00:11 +02:00
Jones f7a6f04792 Make the update checker more accessible to users 2024-09-22 12:54:31 +02:00
Jones e36ba9840c Hide secure-chat toast from 1.20.5+ clients 2024-09-21 18:22:56 +02:00
Jones 40bceb71f1 Use the modern way of registering Velocity commands 2024-09-21 14:41:59 +02:00
Jones 2d3bf91444 Recode bandwidth statistic handling 2024-09-21 14:37:58 +02:00
Jones 45ab258a6f Fix upload-artifacts unable to find artifacts 2024-09-21 14:07:54 +02:00
Jones 600277660c General project base refactoring 2024-09-21 14:05:54 +02:00
MISHAandJones 6918afae52 JAR Downloads via GitHub Actions (#384)
* Update gradle.yml

* Update gradle.yml

* fix: smaller improvements to CI

* fix: wildcard pattern breaking CI

* fix(ci): revert wildcard patterns

---------

Co-authored-by: Jones <admin@jonesdev.xyz>
2024-09-21 13:45:15 +02:00
Jones 07cfc4ad1c Randomize X and Z positions in vehicle check 2024-09-19 17:30:06 +02:00
Jones 65eb9a503f Fix potential issue in the vehicle handler 2024-09-19 17:15:47 +02:00
Jones f5d7cc1511 Small collision check improvements 2024-09-19 16:59:25 +02:00
Jones 2cb0a48657 Add some more smaller movement validations 2024-09-19 16:45:39 +02:00
Jones 646f296039 Don't notify the channel about unhandled packets 2024-09-19 16:29:16 +02:00
Jones a698c26d39 Fix the 1.7.2-1.7.10 minimum Y coordinate issue 2024-09-19 16:21:19 +02:00
Jones 25136d6332 Small micro-optimizations in the packet registry 2024-09-19 13:11:20 +02:00
Jones ee4b1ffef4 Optimize processing of queued logins 2024-09-18 13:11:59 +02:00
Jones fe145836fe Use a better-suited data type for connected players 2024-09-18 12:34:34 +02:00
Jones 47100f4071 Bump Gradle from 8.10 to 8.10.1 2024-09-18 10:52:03 +02:00
Jones 57311996c8 Set Gradle groupId to xyz.jonesdev 2024-09-18 10:49:01 +02:00
Jones 7ad8e48431 Bump bungeecord-proxy from master-SNAPSHOT to 1.21-SNAPSHOT 2024-09-18 10:39:55 +02:00
Jones 154d999b88 Bump velocity-proxy from 3.3.0-SNAPSHOT to 3.4.0-SNAPSHOT 2024-09-18 10:39:04 +02:00
Jones bde637c9c5 Exempt Bedrock players from packet order checks 2024-09-18 10:07:56 +02:00
Jones cb1c62f478 Fix max total packets false positive on 1.7-1.8 2024-09-17 15:20:17 +02:00
Jones 3727742419 Add CaptchaGenerationStartEvent, CaptchaGenerationEndEvent 2024-09-17 14:52:15 +02:00
Jones f79bb38740 Remove a debug message 2024-09-17 14:48:47 +02:00
Jones 7d60ba85e7 Fix custom CAPTCHA background image not working 2024-09-17 14:44:31 +02:00
Jones cec7fa07c2 Fix another potential false positive with Geyser 2024-09-17 13:12:57 +02:00
Jones 2be2380e3f Fix invalid login mappings for 1.18-1.18.1 2024-09-16 14:18:08 +02:00
dependabot[bot] ae99ade03a Bump com.gradleup.shadow from 8.3.0 to 8.3.1 (#382)
Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.0 to 8.3.1.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/8.3.0...8.3.1)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 13:53:09 +02:00
219 changed files with 822 additions and 789 deletions

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[{*.java,*.kt}]
[*.java]
ij_java_wrap_long_lines = true
[{*.md,*.yml}]
+32 -3
View File
@@ -9,9 +9,9 @@ name: Sonar CI
on:
push:
branches: [ "main", "dev" ]
branches: [ "main" ]
pull_request:
branches: [ "main", "dev" ]
branches: [ "main" ]
permissions:
contents: read
@@ -22,12 +22,41 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
uses: gradle/gradle-build-action@v3
with:
arguments: build-sonar
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: bukkit/build/libs/Sonar-Bukkit.jar
if-no-files-found: error
- name: Upload BungeeCord Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: bungeecord/build/libs/Sonar-Bungee.jar
if-no-files-found: error
- name: Upload Velocity Artifact
uses: actions/upload-artifact@v3.1.3
with:
# Artifact name
name: Sonar
# A file, directory or wildcard pattern that describes what to upload
path: velocity/build/libs/Sonar-Velocity.jar
if-no-files-found: error
@@ -9,5 +9,11 @@ dependencies {
compileOnly(rootProject.libs.annotations)
}
tasks {
shadowJar {
archiveFileName = "sonar-api-${rootProject.version}.jar"
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
@@ -97,6 +97,6 @@ public interface Sonar {
@NotNull
static Sonar get() {
return SonarSupplier.get();
return SonarSupplier.sonar;
}
}
@@ -19,20 +19,15 @@ package xyz.jonesdev.sonar.api;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.AlreadyInitializedException;
@UtilityClass
public class SonarSupplier {
private Sonar _sonar;
Sonar sonar;
public void set(final @NotNull Sonar instance) {
if (_sonar != null) {
throw new AlreadyInitializedException("Sonar is already initialized");
public void set(final @NotNull Sonar _sonar) {
if (sonar != null) {
throw new IllegalStateException("Sonar is already initialized");
}
_sonar = instance;
}
public @NotNull Sonar get() {
return _sonar;
sonar = _sonar;
}
}
@@ -26,6 +26,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
import xyz.jonesdev.sonar.api.update.UpdateChecker;
import java.util.*;
@@ -66,11 +67,18 @@ public interface SonarCommand {
// Invoke subcommand
command.invoke(source, args);
}, () -> {
final UpdateChecker.CheckResult checkResult = UpdateChecker.getLastCheckResult();
Component version = Component.text(Sonar.get().getVersion().getFormatted());
if (checkResult.getConfigKey() != null) {
version = version.append(MiniMessage.miniMessage().deserialize(
Sonar.get().getConfig().getMessagesConfig().getString("commands.main." + checkResult.getConfigKey())));
}
// Print standard help message
source.sendMessage(MiniMessage.miniMessage().deserialize(
String.join("<newline>",
Sonar.get().getConfig().getMessagesConfig().getStringList("commands.main.header")),
Placeholder.unparsed("version", Sonar.get().getVersion().getFormatted()),
Placeholder.component("version", version),
Placeholder.unparsed("platform", Sonar.get().getPlatform().getDisplayName()),
Placeholder.unparsed("copyright-year", String.valueOf(Calendar.getInstance().get(Calendar.YEAR)))));
@@ -94,7 +102,7 @@ public interface SonarCommand {
});
}
static void prepareCachedMessages() {
static void prepareCachedTabSuggestions() {
// Don't re-cache tab suggestions
if (!TAB_SUGGESTIONS.isEmpty()) return;
// Cache tab suggestions
@@ -183,10 +183,11 @@ public final class SonarConfiguration {
final String backgroundPath = generalConfig.getString("verification.checks.map-captcha.background");
if (!backgroundPath.isEmpty()) {
try (final InputStream inputStream = new FileInputStream(backgroundPath)) {
verification.map.backgroundImage = inputStream;
} catch (IOException exception) {
Sonar.get().getLogger().error("Could not find background image", exception);
final File backgroundFile = new File(pluginFolder, backgroundPath);
if (backgroundFile.exists()) {
verification.map.backgroundImage = backgroundFile;
} else {
Sonar.get().getLogger().error("Could not find background image {}", backgroundFile.getAbsolutePath());
}
}
@@ -244,7 +245,7 @@ public final class SonarConfiguration {
noPermission = MiniMessage.miniMessage().deserialize(messagesConfig.getString("commands.no-permission"),
Placeholder.component("prefix", prefix));
SonarCommand.prepareCachedMessages();
SonarCommand.prepareCachedTabSuggestions();
supportUrl = messagesConfig.getString("support-url");
header = MiniMessage.miniMessage().deserialize(
@@ -366,7 +367,7 @@ public final class SonarConfiguration {
private int maxDuration;
private int maxTries;
private String alphabet;
private InputStream backgroundImage;
private File backgroundImage;
}
@Getter
@@ -99,7 +99,6 @@ public final class VerifiedPlayerController {
try {
TableUtils.createTableIfNotExists(connectionSource, VerifiedPlayer.class);
} catch (SQLException ignored) {
ignored.printStackTrace();
/*
* This is caused by a duplicate index;
* I know this isn't the best method of handling it,
@@ -17,13 +17,13 @@
package xyz.jonesdev.sonar.api.event;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface SonarEventListener {
/**
* Handles the publishing of an event
*
* @param event [Cancellable] Sonar event
* Handles the publishing of an event through Sonar's API
*/
void handle(final SonarEvent event);
void handle(final @NotNull SonarEvent event);
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.event.impl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
@Getter
@ToString
@RequiredArgsConstructor
public final class CaptchaGenerationEndEvent implements SonarEvent {
private final SystemTimer timer;
private final int amountGenerated;
}
@@ -15,16 +15,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.exception;
package xyz.jonesdev.sonar.api.event.impl;
import org.jetbrains.annotations.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import xyz.jonesdev.sonar.api.event.SonarEvent;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
public final class AlreadyInitializedException extends RuntimeException {
public AlreadyInitializedException(final @NotNull String message) {
super(message);
}
public Throwable fillInStackTrace() {
return this;
}
@Getter
@Setter
@AllArgsConstructor
public final class CaptchaGenerationStartEvent implements SonarEvent {
private CaptchaGenerator captchaGenerator;
}
@@ -22,7 +22,6 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
@@ -37,21 +36,15 @@ import java.util.concurrent.ConcurrentMap;
public final class Fallback {
public static final Fallback INSTANCE = new Fallback();
// Map of all players connected to the server in general
private final ConcurrentMap<InetAddress, Integer> online = new ConcurrentHashMap<>(128);
// Map of all connected IP addresses (used for fast checking)
// TODO: Is there a way to improve this?
// (E.g. no map needed without concurrency issues)
private final ConcurrentMap<InetAddress, Byte> connected = new ConcurrentHashMap<>(256);
// Cache of all blacklisted IP addresses to ensure each entry can expire after the given time
private final ConcurrentMap<InetAddress, Boolean> connected = new ConcurrentHashMap<>(256);
private final FallbackLoginQueue queue = new FallbackLoginQueue();
@Setter
private Cache<String, Integer> blacklist;
@Setter
private long blacklistTime;
@Setter
private CaptchaGenerator captchaGenerator;
private final @NotNull FallbackQueue queue = new FallbackQueue();
@Setter
private Ratelimiter<InetAddress> ratelimiter;
@@ -23,8 +23,6 @@ import lombok.RequiredArgsConstructor;
import xyz.jonesdev.sonar.api.Sonar;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
@@ -32,27 +30,24 @@ import java.util.concurrent.Executors;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public final class FallbackQueue {
public final class FallbackLoginQueue {
// Fixed thread pool executor for all new verifications
private static final int THREAD_POOL_SIZE = Math.min(0x7fff, Runtime.getRuntime().availableProcessors() * 2);
private static final ExecutorService QUEUE_EXECUTOR = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
private final ConcurrentMap<InetAddress, Runnable> players = new ConcurrentHashMap<>(
128, 0.75f, Runtime.getRuntime().availableProcessors());
private final ConcurrentMap<InetAddress, Runnable> players = new ConcurrentHashMap<>(512);
public void poll() {
final int maxQueuePolls = Sonar.get().getConfig().getQueue().getMaxQueuePolls();
int index = 0;
// Iterate through the map and process up to maxQueuePolls entries
final int maxQueuePolls = Math.min(players.size(), Sonar.get().getConfig().getQueue().getMaxQueuePolls());
// No need to initiate an executor service task if nobody is currently queued
if (maxQueuePolls <= 0) return;
// We need to be cautious here since we don't want any concurrency issues
final Iterator<Map.Entry<InetAddress, Runnable>> iterator = players.entrySet().iterator();
while (iterator.hasNext() && index++ < maxQueuePolls) {
// Run the cached runnable
final Map.Entry<InetAddress, Runnable> entry = iterator.next();
QUEUE_EXECUTOR.execute(entry.getValue());
// Remove runnable from the map
iterator.remove();
}
QUEUE_EXECUTOR.execute(() -> {
final var iterator = players.entrySet().iterator();
for (int index = 0; index < maxQueuePolls && iterator.hasNext(); index++) {
iterator.next().getValue().run();
iterator.remove();
}
});
}
}
@@ -18,7 +18,6 @@
package xyz.jonesdev.sonar.api.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.util.ReferenceCountUtil;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
@@ -29,10 +28,7 @@ import java.net.InetAddress;
public interface FallbackUser {
@NotNull
Channel getChannel();
@NotNull
ChannelPipeline getPipeline();
Channel channel();
@NotNull
InetAddress getInetAddress();
@@ -69,8 +65,8 @@ public interface FallbackUser {
* @param msg Message to send to the player
*/
default void write(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().writeAndFlush(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().writeAndFlush(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -81,8 +77,8 @@ public interface FallbackUser {
* sent once all messages are flushed.
*/
default void delayedWrite(final @NotNull Object msg) {
if (getChannel().isActive()) {
getChannel().write(msg, getChannel().voidPromise());
if (channel().isActive()) {
channel().write(msg, channel().voidPromise());
} else {
ReferenceCountUtil.release(msg);
}
@@ -17,9 +17,15 @@
package xyz.jonesdev.sonar.api.fallback.captcha;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
@FunctionalInterface
public interface CaptchaGenerator {
BufferedImage createImage(final char[] answer);
/**
* Generates a {@link java.awt.image.BufferedImage} that shows the answer to the CAPTCHA
*/
@NotNull BufferedImage createImage(final char @NotNull [] answer);
}
@@ -63,10 +63,10 @@ public final class ActionBarNotificationHandler extends NotificationHandler {
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getCurrentIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getCurrentOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", Sonar.get().getStatistics().getTotalIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic-ttl", Sonar.get().getStatistics().getTotalOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -63,10 +63,10 @@ public final class ChatNotificationHandler extends NotificationHandler {
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getCurrentIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getCurrentOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", Sonar.get().getStatistics().getTotalIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic-ttl", Sonar.get().getStatistics().getTotalOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getPerSecondIncomingBandwidthFormatted()),
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getPerSecondOutgoingBandwidthFormatted()),
Placeholder.unparsed("incoming-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalIncomingBandwidth())),
Placeholder.unparsed("outgoing-traffic-ttl", formatMemory(Sonar.get().getStatistics().getTotalOutgoingBandwidth())),
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.api.statistics;
@SuppressWarnings("unused")
public interface SonarStatistics {
long getConnectionsPerSecond();
long getLoginsPerSecond();
long getCurrentIncomingBandwidth();
long getCurrentOutgoingBandwidth();
long getTotalIncomingBandwidth();
long getTotalOutgoingBandwidth();
String getPerSecondIncomingBandwidthFormatted();
String getPerSecondOutgoingBandwidthFormatted();
int getTotalPlayersJoined();
int getTotalPlayersVerified();
int getTotalSuccessfulVerifications();
int getTotalFailedVerifications();
long getCurrentAttemptedVerifications();
int getTotalAttemptedVerifications();
long getCurrentBlacklistSize();
long getTotalBlacklistSize();
}
@@ -15,10 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.update;
package xyz.jonesdev.sonar.api.update;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -35,6 +37,8 @@ import java.util.concurrent.Executors;
@UtilityClass
public class UpdateChecker {
private final ExecutorService ASYNC_EXECUTOR = Executors.newSingleThreadExecutor();
@Getter
private CheckResult lastCheckResult = CheckResult.UNRESOLVED;
public void checkForUpdates() {
ASYNC_EXECUTOR.execute(() -> {
@@ -49,15 +53,19 @@ public class UpdateChecker {
if (convertedCurrentVersion < convertedLatestVersion) {
Sonar.get().getLogger().warn("A new version of Sonar is available: {}", latestStableRelease);
Sonar.get().getLogger().warn("Please make sure to update to the latest version to ensure stability and security:");
Sonar.get().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/tag/{}", latestStableRelease);
Sonar.get().getLogger().warn("https://github.com/jonesdevelopment/sonar/releases/latest");
lastCheckResult = CheckResult.OUTDATED_VERSION;
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().getGitBranch().equals("main")) {
Sonar.get().getLogger().warn("You are currently using an unreleased version of Sonar!");
Sonar.get().getLogger().warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
} else {
lastCheckResult = CheckResult.UNSTABLE_VERSION;
} else if (lastCheckResult != CheckResult.LATEST_VERSION) {
Sonar.get().getLogger().info("You are currently using the latest stable release of Sonar!");
lastCheckResult = CheckResult.LATEST_VERSION;
}
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Unable to retrieve version information: {}", throwable);
lastCheckResult = CheckResult.API_ERROR;
}
});
}
@@ -99,4 +107,16 @@ public class UpdateChecker {
final int patch = Integer.parseInt(convertedParts[2]);
return convertedMajor + convertedMinor + patch; // sum the converted version parts
}
@Getter
@RequiredArgsConstructor
public enum CheckResult {
API_ERROR("api-error"),
OUTDATED_VERSION("outdated-version"),
UNSTABLE_VERSION("unstable-version"),
LATEST_VERSION(null),
UNRESOLVED(null);
private final String configKey;
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ allprojects {
tasks {
// This is a small wrapper tasks to simplify the building process
register("build-sonar") {
val subprojects = listOf("api", "common", "bukkit", "bungee", "velocity")
val subprojects = listOf("api", "captcha", "common", "bukkit", "bungeecord", "velocity")
val buildTasks = subprojects.flatMap { listOf("$it:clean", "$it:shadowJar") }
dependsOn(buildTasks)
}
File renamed without changes.
@@ -34,12 +34,14 @@ import java.net.InetSocketAddress;
import java.util.Objects;
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_INBOUND_HANDLER;
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
import static xyz.jonesdev.sonar.common.fallback.protocol.packets.handshake.HandshakePacket.*;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.DEBUG;
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
private FallbackPacketRegistry.ProtocolRegistry registry;
FallbackBukkitInboundHandler() {
updateRegistry(FallbackPacketRegistry.HANDSHAKE, DEFAULT_PROTOCOL_VERSION);
@@ -53,17 +55,13 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
};
}
private FallbackPacketRegistry.ProtocolRegistry registry;
private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = ProtocolVersion.MINECRAFT_1_7_2;
public void updateRegistry(final @NotNull FallbackPacketRegistry registry,
final @NotNull ProtocolVersion protocolVersion) {
this.registry = registry.getProtocolRegistry(SERVERBOUND, protocolVersion);
this.registry = registry.getProtocolRegistry(FallbackPacketRegistry.Direction.SERVERBOUND, protocolVersion);
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof ByteBuf) {
final ByteBuf byteBuf = (ByteBuf) msg;
@@ -119,7 +117,7 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
case LOGIN:
case TRANSFER:
// Let the actual handler know about the handshake packet
handleHandshake(ctx.channel(), handshake.getHostname(), handshake.getProtocolVersionId());
handleHandshake(ctx, handshake.getHostname(), handshake.getProtocolVersionId());
// Be ready for the next packet (which is supposed to be a login packet)
updateRegistry(FallbackPacketRegistry.LOGIN, Objects.requireNonNull(protocolVersion));
break;
@@ -132,9 +130,10 @@ final class FallbackBukkitInboundHandler extends FallbackInboundHandlerAdapter {
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Let Sonar process the login packet
handleLogin(ctx.channel(), ctx, () -> {
handleLogin(ctx, () -> {
byteBuf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(byteBuf.retain());
// TODO: recode this?
final ChannelHandler inboundHandler = ctx.pipeline().remove(FALLBACK_INBOUND_HANDLER);
if (inboundHandler != null) {
channelRemovalListener.accept(ctx.pipeline(), FALLBACK_INBOUND_HANDLER, inboundHandler);
@@ -26,8 +26,8 @@ import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.netty.FallbackInjectedChannelInitializer;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -26,6 +26,8 @@ dependencies {
tasks {
shadowJar {
relocate("net.kyori", "xyz.jonesdev.sonar.libs.kyori")
archiveFileName = "Sonar-Bungee.jar" // Hardcode a shortened version of the name
}
}
@@ -17,7 +17,6 @@
package xyz.jonesdev.sonar.bungee.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
@@ -26,8 +25,8 @@ import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LoginRequest;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandlerAdapter;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -49,42 +48,42 @@ final class FallbackBungeeInboundHandler extends FallbackInboundHandlerAdapter {
}
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object msg) throws Exception {
if (msg instanceof PacketWrapper) {
final PacketWrapper packetWrapper = (PacketWrapper) msg;
final DefinedPacket wrappedPacket = packetWrapper.packet;
// Skip any unknown or invalid packets
if (wrappedPacket != null) {
final Channel channel = ctx.channel();
// Intercept any handshake packet by the client
if (wrappedPacket instanceof Handshake) {
final Handshake handshake = (Handshake) wrappedPacket;
// We don't care about server pings; remove the handler
if (handshake.getRequestedProtocol() == STATUS) {
ctx.pipeline().remove(this);
} else {
handleHandshake(channel, handshake.getHost(), handshake.getProtocolVersion());
handleHandshake(ctx, handshake.getHost(), handshake.getProtocolVersion());
}
}
// Intercept any server login packet by the client
else if (wrappedPacket instanceof LoginRequest) {
} else if (wrappedPacket instanceof LoginRequest) {
// Make sure to use the potentially modified, real IP
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
final HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
// This might sometimes happen if the client unexpectedly disconnects
if (handlerBoss != null) {
// Get the real IP address of the player through the channel wrapper
final ChannelWrapper channelWrapper;
try {
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
} catch (final Throwable throwable) {
throw new ReflectiveOperationException(throwable);
}
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
handleLogin(ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
// We've done our job - deject this pipeline
ctx.pipeline().remove(this);
// Make sure to mark this packet as released
packetWrapper.trySingleRelease();
// Let Sonar process the login packet
handleLogin(channel, ctx, () -> ctx.fireChannelRead(msg), loginRequest.getData(), socketAddress);
return;
}
}
}
File renamed without changes.
@@ -33,8 +33,8 @@ import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Random;
@@ -69,12 +69,12 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
}
}
private final int width, height;
private final @Nullable InputStream background;
private BufferedImage backgroundImage;
private final int width = 128, height = 128;
private final @Nullable File background;
private @Nullable BufferedImage backgroundImage;
@Override
public @NotNull BufferedImage createImage(final char[] answer) {
public @NotNull BufferedImage createImage(final char @NotNull [] answer) {
final BufferedImage image = createBackgroundImage();
final Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@@ -91,16 +91,22 @@ public final class StandardCaptchaGenerator implements CaptchaGenerator {
}
private @NotNull BufferedImage createBackgroundImage() {
// Create a new image buffer with a 3-byte color spectrum
final BufferedImage image = new BufferedImage(width, height, TYPE_3BYTE_BGR);
if (background == null) {
final BufferedImage image = new BufferedImage(width, height, TYPE_3BYTE_BGR);
// Fill the entire image with a noise texture
return FBM.filter(image, image);
}
try {
return backgroundImage == null ? backgroundImage = ImageIO.read(background) : backgroundImage;
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
// Try to load the image from a file if it doesn't exist yet
if (backgroundImage == null) {
try {
backgroundImage = ImageIO.read(background);
} catch (IOException exception) {
throw new IllegalStateException("Could not read background image", exception);
}
}
image.createGraphics().drawImage(backgroundImage, 0, 0, Color.WHITE, null);
return image;
}
private void applyRandomColorGradient(final @NotNull Graphics2D graphics) {
@@ -28,7 +28,7 @@ import java.util.Random;
public class Main {
public void main(final String... args) throws IOException {
final Random random = new Random();
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(128, 128, null);
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(null);
final long start = System.currentTimeMillis();
final char[] dictionary = {'a', 'b', 'c', 'd', 'e', 'f'/*, 'g'*/, 'h'/*, 'i'*/, 'j',
File renamed without changes.
@@ -39,10 +39,9 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
import xyz.jonesdev.sonar.common.fallback.ratelimit.CaffeineCacheRatelimiter;
import xyz.jonesdev.sonar.common.fallback.ratelimit.NoopCacheRatelimiter;
import xyz.jonesdev.sonar.common.service.SonarServiceManager;
import xyz.jonesdev.sonar.common.service.ScheduledServiceManager;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
import xyz.jonesdev.sonar.common.subcommand.*;
import xyz.jonesdev.sonar.common.update.UpdateChecker;
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
import java.io.File;
@@ -99,13 +98,6 @@ public abstract class SonarBootstrap<T> implements Sonar {
}
public final void initialize() {
// Check if the branch is not the main branch to warn about unstable versions
if (!getVersion().getGitBranch().equals("main")) {
getLogger().warn("You are currently using an unofficial experimental branch.");
getLogger().warn("It is highly recommended to use the latest stable release of Sonar:");
getLogger().warn("https://github.com/jonesdevelopment/sonar/releases");
}
getLogger().info("Successfully booted in {}s!", launchTimer);
getLogger().info("Initializing shared components...");
@@ -121,7 +113,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Start threads
getLogger().info("Starting all managed threads...");
SonarServiceManager.start();
ScheduledServiceManager.start();
// Done
getLogger().info("Done ({}s)!", launchTimer);
@@ -129,12 +121,6 @@ public abstract class SonarBootstrap<T> implements Sonar {
// An error has occurred
getLogger().error("An error has occurred while launching Sonar: {}", throwable);
throwable.printStackTrace(System.err);
return; // Do not check for updates if the launch failed
}
// Check if a new version has been released if enabled in the configuration
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("general.check-for-updates")) {
UpdateChecker.checkForUpdates();
}
}
@@ -190,7 +176,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
// Initialize the shutdown process
getLogger().info("Starting shutdown process...");
// Interrupt threads
SonarServiceManager.stop();
ScheduledServiceManager.stop();
// Close database connection if present
if (verifiedPlayerController != null) {
verifiedPlayerController.close();
@@ -25,9 +25,7 @@ import io.netty.channel.ChannelPromise;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import static xyz.jonesdev.sonar.common.statistics.BandwidthStatistics.INCOMING;
import static xyz.jonesdev.sonar.common.statistics.BandwidthStatistics.OUTGOING;
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
@ChannelHandler.Sharable
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -35,27 +33,22 @@ public final class FallbackBandwidthHandler extends ChannelDuplexHandler {
public static final ChannelHandler INSTANCE = new FallbackBandwidthHandler();
@Override
public void channelRead(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object msg) throws Exception {
// We can only get the size of a message if it's a ByteBuf
public void channelRead(final ChannelHandlerContext ctx, final @NotNull Object msg) throws Exception {
if (msg instanceof ByteBuf) {
// Increment the incoming traffic by the number of readable bytes
INCOMING.increment(((ByteBuf) msg).readableBytes());
final int readableBytes = ((ByteBuf) msg).readableBytes();
GlobalSonarStatistics.perSecondIncomingTraffic += readableBytes;
}
// Make sure to let the server handle the rest
ctx.fireChannelRead(msg);
}
@Override
public void write(final @NotNull ChannelHandlerContext ctx,
final @NotNull Object msg,
final ChannelPromise promise) throws Exception {
// We can only get the size of a message if it's a ByteBuf
public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception {
if (msg instanceof ByteBuf) {
// Increment the outgoing traffic by the number of readable bytes
OUTGOING.increment(((ByteBuf) msg).readableBytes());
final int readableBytes = ((ByteBuf) msg).readableBytes();
GlobalSonarStatistics.perSecondOutgoingTraffic += readableBytes;
}
// Make sure to let the server handle the rest
ctx.write(msg, promise);
}
}
@@ -49,11 +49,8 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
/**
* Validates and handles incoming handshake packets
*
* @param hostname Hostname (server address) sent by the client
* @param protocol Protocol version number sent by the client
*/
protected final void handleHandshake(final @NotNull Channel channel,
protected final void handleHandshake(final @NotNull ChannelHandlerContext ctx,
final @NotNull String hostname,
final int protocol) throws Exception {
// Check if the hostname is invalid
@@ -65,20 +62,14 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
protocolVersion = ProtocolVersion.fromId(protocol);
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
ctx.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
}
/**
* Validates and handles incoming login packets
*
* @param channel Forwarded client channel
* @param loginPacket Login packet sent by the client
* @param username Username sent by the client
* @param socketAddress Socket address of the client
*/
protected final void handleLogin(final @NotNull Channel channel,
final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable loginPacket,
protected final void handleLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull Runnable initialLoginAction,
final @NotNull String username,
final @NotNull InetSocketAddress socketAddress) throws Exception {
// Check if the player has already sent a login packet
@@ -86,7 +77,7 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
throw QuietDecoderException.INSTANCE;
}
// Connections from unknown protocol versions will be discarded
// as this is the safest way of handling unwanted connections
// as this is the safest way of handling unwanted connections.
// Sonar does not support snapshots or Minecraft versions older than 1.7.2
if (protocolVersion.isUnknown()) {
throw QuietDecoderException.INSTANCE;
@@ -95,17 +86,17 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
this.username = username;
final InetAddress inetAddress = socketAddress.getAddress();
channel.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
ctx.pipeline().get(FallbackInboundHandler.class).setInetAddress(inetAddress);
// Check if Fallback is already verifying a player with the same IP address
if (Sonar.get().getFallback().getConnected().containsKey(inetAddress)) {
customDisconnect(channel, protocolVersion, alreadyVerifying);
customDisconnect(ctx.channel(), alreadyVerifying, protocolVersion);
return;
}
// Check if the protocol ID of the player is not allowed to enter the server
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols().contains(protocolVersion.getProtocol())) {
customDisconnect(channel, protocolVersion, protocolBlacklisted);
customDisconnect(ctx.channel(), protocolBlacklisted, protocolVersion);
return;
}
@@ -115,34 +106,34 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
if (limit > 0) {
final int score = Sonar.get().getFallback().getBlacklist().asMap().getOrDefault(hostAddress, 0);
if (score >= limit) {
customDisconnect(channel, protocolVersion, blacklisted);
customDisconnect(ctx.channel(), blacklisted, protocolVersion);
return;
}
}
// Don't continue the verification process if the verification is disabled
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Completely skip Geyser connections if configured
final boolean geyser = GeyserUtil.isGeyserConnection(channel, socketAddress);
final boolean geyser = GeyserUtil.isGeyserConnection(ctx.channel(), socketAddress);
if (geyser && !Sonar.get().getConfig().getVerification().isCheckGeyser()) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Make sure we actually have to verify the player
final String fingerprint = FingerprintingUtil.getFingerprint(username, hostAddress);
if (Sonar.get().getVerifiedPlayerController().getCache().contains(fingerprint)) {
initialLogin(ctx, inetAddress, loginPacket);
initialLogin(ctx.channel(), inetAddress, initialLoginAction);
return;
}
// Check if the IP address is currently being rate-limited
if (!Sonar.get().getFallback().getRatelimiter().attempt(inetAddress)) {
customDisconnect(channel, protocolVersion, reconnectedTooFast);
customDisconnect(ctx.channel(), reconnectedTooFast, protocolVersion);
return;
}
@@ -150,10 +141,10 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
rewriteProtocol(ctx, channelRemovalListener);
// Queue the connection for further processing
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, action) -> {
Sonar.get().getFallback().getQueue().getPlayers().compute(inetAddress, (__, runnable) -> {
// Check if the player is already queued since we don't want bots to flood the queue
if (action != null) {
customDisconnect(channel, protocolVersion, alreadyQueued);
if (runnable != null) {
customDisconnect(ctx.channel(), alreadyQueued, protocolVersion);
// Remove other instances of this IP address from the queue
return null;
}
@@ -162,16 +153,41 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
// Check if the username matches the valid name regex to prevent
// UTF-16 names or other types of exploits
if (!Sonar.get().getConfig().getVerification().getValidNameRegex().matcher(username).matches()) {
customDisconnect(channel, protocolVersion, invalidUsername);
customDisconnect(ctx.channel(), invalidUsername, protocolVersion);
return;
}
// Create an instance for the user and let the verification handler take over the channel
new FallbackUserWrapper(channel, inetAddress, protocolVersion, username, fingerprint, geyser);
new FallbackUserWrapper(ctx.channel(), inetAddress, protocolVersion, username, fingerprint, geyser);
};
});
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*/
protected final void initialLogin(final @NotNull Channel channel,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(channel, tooManyOnlinePerIP, protocolVersion);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
/**
* Removes all pipelines and rewrites them using our custom handlers
*/
@@ -196,58 +212,24 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
TimeUnit.MILLISECONDS));
}
/**
* Executes the maximum accounts per IP limit check before letting the player join
*
* @param ctx Forwarded client channel context
* @param inetAddress Resolved client IP address
* @param loginPacket Login packet sent by the client
*/
protected final void initialLogin(final @NotNull ChannelHandlerContext ctx,
final @NotNull InetAddress inetAddress,
final @NotNull Runnable loginPacket) throws Exception {
Sonar.get().getFallback().getOnline().compute(inetAddress, (__, count) -> {
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
// Skip the maximum online per IP check if it's disabled in the configuration
if (count != null && maxOnlinePerIp > 0) {
// Check if the number of online players using the same IP address as
// the connecting player is greater than the configured amount
if (count >= maxOnlinePerIp) {
customDisconnect(ctx.channel(), protocolVersion, tooManyOnlinePerIP);
return count + 1;
}
}
// Let the server know about the login packet
loginPacket.run();
// Increment the number of accounts with the same IP
return count == null ? 1 : count + 1;
});
}
/**
* Disconnect the player before verification (during login)
* by replacing the encoder before running the method.
*
* @param packet Disconnect packet
* @param channel Channel of the player
* @param protocolVersion Protocol version of the player
*/
private void customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
private static void customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final ProtocolVersion protocolVersion) {
if (channel.isActive()) {
if (channel.eventLoop().inEventLoop()) {
_customDisconnect(channel, protocolVersion, packet);
_customDisconnect(channel, packet, protocolVersion);
} else {
channel.eventLoop().execute(() -> _customDisconnect(channel, protocolVersion, packet));
channel.eventLoop().execute(() -> _customDisconnect(channel, packet, protocolVersion));
}
}
}
private void _customDisconnect(final @NotNull Channel channel,
final @NotNull ProtocolVersion protocolVersion,
final @NotNull FallbackPacket packet) {
/**
* Disconnect the player before verification (during login)
*/
private static void _customDisconnect(final @NotNull Channel channel,
final @NotNull FallbackPacket packet,
final ProtocolVersion protocolVersion) {
// Remove the connection handler pipeline to completely take over the channel
final String handler = Sonar.get().getPlatform().getConnectionHandler();
if (channel.pipeline().context(handler) != null) {
@@ -18,10 +18,10 @@
package xyz.jonesdev.sonar.common.fallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
@@ -50,8 +50,8 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.closeWith;
@Getter
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
public final class FallbackUserWrapper implements FallbackUser {
@Accessors(fluent = true)
private final Channel channel;
private final ChannelPipeline pipeline;
private final InetAddress inetAddress;
private final ProtocolVersion protocolVersion;
private final String fingerprint;
@@ -68,7 +68,6 @@ public final class FallbackUserWrapper implements FallbackUser {
final @NotNull String fingerprint,
final boolean geyser) {
this.channel = channel;
this.pipeline = channel.pipeline();
this.inetAddress = inetAddress;
this.protocolVersion = protocolVersion;
this.username = username;
@@ -99,12 +98,12 @@ public final class FallbackUserWrapper implements FallbackUser {
}
// Mark the player as connected by caching them in a map of verifying players
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> (byte) 0);
Sonar.get().getFallback().getConnected().compute(inetAddress, (__, v) -> true);
// Replace normal encoder to allow custom packets
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
channel.pipeline().addFirst(FALLBACK_FRAME_ENCODER, FallbackVarIntLengthEncoder.INSTANCE);
channel.pipeline().addLast(FALLBACK_PACKET_ENCODER, newEncoder);
// Send LoginSuccess packet to make the client think they are joining the server
write(loginSuccess);
@@ -114,19 +113,19 @@ public final class FallbackUserWrapper implements FallbackUser {
// Replace normal decoder to allow custom packets
final FallbackPacketDecoder newDecoder = new FallbackPacketDecoder(protocolVersion);
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
pipeline.addLast(FALLBACK_PACKET_DECODER, newDecoder);
channel.pipeline().addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
channel.pipeline().addLast(FALLBACK_PACKET_DECODER, newDecoder);
// Listen for all incoming packets by setting the packet listener
newDecoder.setListener(new FallbackPreJoinHandler(this));
// Make sure to catch all exceptions during the verification
pipeline.addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
channel.pipeline().addLast(FALLBACK_TAIL_EXCEPTIONS, FallbackTailExceptionsHandler.INSTANCE);
});
}
@Override
public void disconnect(final @NotNull Component reason) {
final FallbackPacketEncoder encoder = pipeline.get(FallbackPacketEncoder.class);
final FallbackPacketEncoder encoder = channel.pipeline().get(FallbackPacketEncoder.class);
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
}
@@ -24,9 +24,9 @@ import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.SonarPlatform;
import xyz.jonesdev.sonar.api.exception.ReflectiveOperationException;
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
import xyz.jonesdev.sonar.common.util.exception.ReflectiveOperationException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -96,10 +96,9 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
// Let our verification handler process the packet
if (listener != null) {
listener.handle(packet);
// Make sure to let the timeout handler know about this packet
ctx.fireChannelRead(packet);
}
// Fire channel read to avoid timeout
ctx.fireChannelRead(packet);
} finally {
// Release the ByteBuf to avoid memory leaks
byteBuf.release();
@@ -50,7 +50,7 @@ public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPa
final ByteBuf out) throws Exception {
final FallbackPacket originalPacket = packet instanceof FallbackPacketSnapshot
? ((FallbackPacketSnapshot) packet).getOriginalPacket() : packet;
final int packetId = protocolRegistry.getPacketId(originalPacket);
final int packetId = protocolRegistry.getPacketId(originalPacket.getClass());
writeVarInt(out, packetId);
packet.encode(out, protocolVersion);
}
@@ -20,8 +20,8 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import lombok.AccessLevel;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
@@ -32,7 +32,10 @@ import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginStartPacke
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
import java.util.*;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
@@ -64,11 +67,11 @@ public enum FallbackPacketRegistry {
map(0x01, MINECRAFT_1_20_2, true),
map(0x02, MINECRAFT_1_20_5, true));
clientbound.register(FinishConfigurationPacket.class, FinishConfigurationPacket::new,
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
map(0x02, MINECRAFT_1_20_2, true),
map(0x03, MINECRAFT_1_20_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false),
map(0x04, MINECRAFT_1_20_5, false));
map(0x03, MINECRAFT_1_20_2, true),
map(0x04, MINECRAFT_1_20_5, true));
clientbound.register(RegistryDataPacket.class, RegistryDataPacket::new,
map(0x05, MINECRAFT_1_20_2, true),
map(0x07, MINECRAFT_1_20_5, true));
@@ -104,20 +107,20 @@ public enum FallbackPacketRegistry {
map(0x29, MINECRAFT_1_20_2, true),
map(0x2B, MINECRAFT_1_20_5, true));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false),
map(0x20, MINECRAFT_1_14, false),
map(0x21, MINECRAFT_1_15, false),
map(0x20, MINECRAFT_1_16, false),
map(0x1F, MINECRAFT_1_16_2, false),
map(0x21, MINECRAFT_1_17, false),
map(0x1E, MINECRAFT_1_19, false),
map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false),
map(0x23, MINECRAFT_1_19_4, false),
map(0x24, MINECRAFT_1_20_2, false),
map(0x26, MINECRAFT_1_20_5, false));
map(0x00, MINECRAFT_1_7_2, true),
map(0x1F, MINECRAFT_1_9, true),
map(0x21, MINECRAFT_1_13, true),
map(0x20, MINECRAFT_1_14, true),
map(0x21, MINECRAFT_1_15, true),
map(0x20, MINECRAFT_1_16, true),
map(0x1F, MINECRAFT_1_16_2, true),
map(0x21, MINECRAFT_1_17, true),
map(0x1E, MINECRAFT_1_19, true),
map(0x20, MINECRAFT_1_19_1, true),
map(0x1F, MINECRAFT_1_19_3, true),
map(0x23, MINECRAFT_1_19_4, true),
map(0x24, MINECRAFT_1_20_2, true),
map(0x26, MINECRAFT_1_20_5, true));
clientbound.register(DisconnectPacket.class, DisconnectPacket::new,
map(0x40, MINECRAFT_1_7_2, true),
map(0x1A, MINECRAFT_1_9, true),
@@ -214,19 +217,19 @@ public enum FallbackPacketRegistry {
map(0x5D, MINECRAFT_1_20_3, true),
map(0x5F, MINECRAFT_1_20_5, true));
clientbound.register(VehicleMovePacket.class, VehicleMovePacket::new,
map(0x29, MINECRAFT_1_9, false),
map(0x2B, MINECRAFT_1_13, false),
map(0x2C, MINECRAFT_1_14, false),
map(0x2D, MINECRAFT_1_15, false),
map(0x2C, MINECRAFT_1_16, false),
map(0x2B, MINECRAFT_1_16_2, false),
map(0x2C, MINECRAFT_1_17, false),
map(0x29, MINECRAFT_1_19, false),
map(0x2B, MINECRAFT_1_19_1, false),
map(0x2A, MINECRAFT_1_19_3, false),
map(0x2E, MINECRAFT_1_19_4, false),
map(0x2F, MINECRAFT_1_20_2, false),
map(0x31, MINECRAFT_1_20_5, false));
map(0x29, MINECRAFT_1_9, true),
map(0x2B, MINECRAFT_1_13, true),
map(0x2C, MINECRAFT_1_14, true),
map(0x2D, MINECRAFT_1_15, true),
map(0x2C, MINECRAFT_1_16, true),
map(0x2B, MINECRAFT_1_16_2, true),
map(0x2C, MINECRAFT_1_17, true),
map(0x29, MINECRAFT_1_19, true),
map(0x2B, MINECRAFT_1_19_1, true),
map(0x2A, MINECRAFT_1_19_3, true),
map(0x2E, MINECRAFT_1_19_4, true),
map(0x2F, MINECRAFT_1_20_2, true),
map(0x31, MINECRAFT_1_20_5, true));
clientbound.register(SpawnEntityPacket.class, SpawnEntityPacket::new,
map(0x0E, MINECRAFT_1_7_2, true),
map(0x00, MINECRAFT_1_9, true),
@@ -320,19 +323,19 @@ public enum FallbackPacketRegistry {
map(0x5A, MINECRAFT_1_20_3, true),
map(0x5C, MINECRAFT_1_20_5, true));
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
map(0x02, MINECRAFT_1_7_2, false),
map(0x0F, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_13, false),
map(0x0F, MINECRAFT_1_15, false),
map(0x0E, MINECRAFT_1_16, false),
map(0x0F, MINECRAFT_1_17, false),
map(0x5F, MINECRAFT_1_19, false),
map(0x62, MINECRAFT_1_19_1, false),
map(0x60, MINECRAFT_1_19_3, false),
map(0x64, MINECRAFT_1_19_4, false),
map(0x67, MINECRAFT_1_20_2, false),
map(0x69, MINECRAFT_1_20_3, false),
map(0x6C, MINECRAFT_1_20_5, false));
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, true),
map(0x0E, MINECRAFT_1_16, true),
map(0x0F, MINECRAFT_1_17, true),
map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true),
map(0x64, MINECRAFT_1_19_4, true),
map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true));
clientbound.register(GameEventPacket.class, GameEventPacket::new,
map(0x20, MINECRAFT_1_20_3, true),
map(0x22, MINECRAFT_1_20_5, true));
@@ -603,30 +606,28 @@ public enum FallbackPacketRegistry {
return (direction == Direction.SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version);
}
public static class PacketRegistry {
private final Map<ProtocolVersion, ProtocolRegistry> versions;
public static final class PacketRegistry {
private final Map<ProtocolVersion, ProtocolRegistry> registries = new EnumMap<>(ProtocolVersion.class);
PacketRegistry() {
final Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class);
for (final ProtocolVersion version : ProtocolVersion.values()) {
if (!version.isUnknown()) {
mutableVersions.put(version, new ProtocolRegistry(version));
for (final ProtocolVersion protocolVersion : ProtocolVersion.values()) {
if (!protocolVersion.isUnknown()) {
registries.put(protocolVersion, new ProtocolRegistry(protocolVersion));
}
}
this.versions = Collections.unmodifiableMap(mutableVersions);
}
ProtocolRegistry getProtocolRegistry(final ProtocolVersion version) {
final ProtocolRegistry registry = versions.get(version);
private @NotNull ProtocolRegistry getProtocolRegistry(final @NotNull ProtocolVersion protocolVersion) {
final ProtocolRegistry registry = registries.get(protocolVersion);
if (registry == null) {
throw new IllegalArgumentException("Could not find data for protocol version " + version);
throw new IllegalArgumentException("Could not find data for protocol version " + protocolVersion);
}
return registry;
}
<P extends FallbackPacket> void register(final Class<P> clazz,
final Supplier<P> packetSupplier,
final PacketMapping @NotNull ... mappings) {
private <T extends FallbackPacket> void register(final @NotNull Class<T> clazz,
final @NotNull Supplier<T> supplier,
final PacketMapping @NotNull ... mappings) {
if (mappings.length == 0) {
throw new IllegalArgumentException("At least one mapping must be provided.");
}
@@ -636,21 +637,21 @@ public enum FallbackPacketRegistry {
final PacketMapping next = (i + 1 < mappings.length) ? mappings[i + 1] : current;
final ProtocolVersion from = current.protocolVersion;
final ProtocolVersion to = getProtocolVersion(current, next);
final ProtocolVersion to = current == next ? LATEST_VERSION : next.protocolVersion;
for (final ProtocolVersion protocol : EnumSet.range(from, to)) {
if (protocol == to && next != current) {
for (final ProtocolVersion protocolVersion : EnumSet.range(from, to)) {
if (protocolVersion == to && next != current) {
break;
}
final ProtocolRegistry registry = versions.get(protocol);
final ProtocolRegistry registry = registries.get(protocolVersion);
if (registry == null) {
throw new IllegalArgumentException("Unknown protocol version " + from);
throw new IllegalArgumentException("Unknown protocol version " + protocolVersion);
}
if (registry.packetIdToSupplier.containsKey(current.id)) {
if (registry.packetIdToSupplier.containsKey(current.packetId)) {
throw new IllegalArgumentException("Can not register class " + clazz.getSimpleName()
+ " with id " + current.id + " for " + registry.protocolVersion
+ " with id " + current.packetId + " for " + registry.protocolVersion
+ " because another packet is already registered");
}
@@ -660,18 +661,12 @@ public enum FallbackPacketRegistry {
}
if (!current.encodeOnly) {
registry.packetIdToSupplier.put(current.id, packetSupplier);
registry.packetIdToSupplier.put(current.packetId, supplier);
}
registry.packetClassToId.put(clazz, current.id);
registry.packetClassToId.put(clazz, current.packetId);
}
}
}
@NotNull
private static ProtocolVersion getProtocolVersion(final @NotNull PacketMapping current,
final @NotNull PacketMapping next) {
return current == next ? LATEST_VERSION : next.protocolVersion;
}
}
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -682,43 +677,38 @@ public enum FallbackPacketRegistry {
private final Map<Class<? extends FallbackPacket>, Integer> packetClassToId =
new HashMap<>(16, 0.5f);
public FallbackPacket createPacket(final int id) {
final Supplier<? extends FallbackPacket> supplier = packetIdToSupplier.get(id);
if (supplier == null) {
return null;
}
return supplier.get();
public FallbackPacket createPacket(final int packetId) {
final var supplier = packetIdToSupplier.get(packetId);
return supplier == null ? null : supplier.get();
}
public int getPacketId(final @NotNull FallbackPacket packet) {
final int id = packetClassToId.getOrDefault(packet.getClass(), Integer.MIN_VALUE);
if (id == Integer.MIN_VALUE) {
throw new IllegalArgumentException("Could not find packet");
public int getPacketId(final @NotNull Class<? extends FallbackPacket> clazz) {
final int packetId = packetClassToId.getOrDefault(clazz, -1);
if (packetId == -1) {
throw new IllegalArgumentException("Could not find packet ID for " + clazz.getSimpleName());
}
return id;
return packetId;
}
}
@Data
@ToString
public static final class PacketMapping {
private final int id;
private final int packetId;
private final ProtocolVersion protocolVersion;
private final boolean encodeOnly;
PacketMapping(final int id,
PacketMapping(final int packetId,
final ProtocolVersion protocolVersion,
final boolean encodeOnly) {
this.id = id;
this.packetId = packetId;
this.protocolVersion = protocolVersion;
this.encodeOnly = encodeOnly;
}
}
private static @NotNull PacketMapping map(final int id,
private static @NotNull PacketMapping map(final int packetId,
final ProtocolVersion protocolVersion,
final boolean encodeOnly) {
return new PacketMapping(id, protocolVersion, encodeOnly);
return new PacketMapping(packetId, protocolVersion, encodeOnly);
}
}
@@ -24,6 +24,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationStartEvent;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
@@ -43,9 +44,8 @@ import java.util.UUID;
public class FallbackPreparer {
private final Random RANDOM = new Random();
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt(50);
public static final int VEHICLE_BOAT_ENTITY_ID = PLAYER_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final int VEHICLE_MINECART_ENTITY_ID = VEHICLE_BOAT_ENTITY_ID + 1 + RANDOM.nextInt(10);
public static final int PLAYER_ENTITY_ID = RANDOM.nextInt();
public static final int VEHICLE_ENTITY_ID = RANDOM.nextInt();
public static final CompoundBinaryTag MAP_ITEM_NBT = CompoundBinaryTag.builder()
.put("map", IntBinaryTag.intBinaryTag(0)) // map type
@@ -75,10 +75,11 @@ public class FallbackPreparer {
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
public final FallbackPacket START_WRITING_CHUNKS = new GameEventPacket(13, 0);
public final static FallbackPacket CACHED_HELD_ITEM_RESET = new SetHeldItemPacket(~4);
public final static FallbackPacket CACHED_HELD_ITEM_SLOT = new SetHeldItemPacket(~4);
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new KeepAlivePacket(RANDOM.nextInt());
public static FallbackPacket loginSuccess;
public FallbackPacket welcomeMessage;
public FallbackPacket enterCodeMessage;
public FallbackPacket incorrectCaptcha;
public static FallbackPacket joinGame;
@@ -98,8 +99,7 @@ public class FallbackPreparer {
public FallbackPacket teleportMinecart;
public FallbackPacket spawnBoatEntity;
public FallbackPacket spawnMinecartEntity;
public FallbackPacket setBoatPassengers;
public FallbackPacket setMinecartPassengers;
public FallbackPacket setPassengers;
public FallbackPacket[] xpCountdown;
public static int maxMovementTick, dynamicSpawnYPosition, maxTotalPacketsSent;
@@ -116,15 +116,15 @@ public class FallbackPreparer {
// Prepare JoinGame packet
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
Sonar.get().getConfig().getVerification().getGamemode().getId(),
RANDOM.nextLong(), false, 0,
true, false, false,
new String[]{"minecraft:overworld"}, "minecraft:overworld"));
Sonar.get().getConfig().getVerification().getGamemode().getId(), RANDOM.nextLong(),
0, 0, false, true, false,
false, false, false, true,
new String[]{"minecraft:overworld"}, "minecraft:overworld", "flat"));
// Prepare the gravity check
maxMovementTick = Sonar.get().getConfig().getVerification().getGravity().getMaxMovementTicks();
double motionY = 0, fallDistance = 1;
double motionY = 0, fallDistance = 0;
for (int i = 0; i < maxMovementTick; i++) {
motionY = (motionY - 0.1) * 0.98f;
@@ -180,15 +180,26 @@ public class FallbackPreparer {
}
// Prepare packets for the vehicle check
removeBoat = new RemoveEntitiesPacket(VEHICLE_BOAT_ENTITY_ID);
removeBoat = new RemoveEntitiesPacket(VEHICLE_ENTITY_ID);
teleportMinecart = new TeleportEntityPacket(
VEHICLE_MINECART_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_BOAT_ENTITY_ID, EntityType.BOAT, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(
VEHICLE_MINECART_ENTITY_ID, EntityType.MINECART, SPAWN_X_POSITION, IN_AIR_Y_POSITION, SPAWN_Z_POSITION));
setBoatPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_BOAT_ENTITY_ID, PLAYER_ENTITY_ID));
setMinecartPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_MINECART_ENTITY_ID, PLAYER_ENTITY_ID));
VEHICLE_ENTITY_ID, SPAWN_X_POSITION, IN_VOID_Y_POSITION, SPAWN_Z_POSITION, false);
spawnBoatEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
EntityType.BOAT, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
spawnMinecartEntity = new FallbackPacketSnapshot(new SpawnEntityPacket(VEHICLE_ENTITY_ID,
EntityType.MINECART, RANDOM.nextInt(16), IN_AIR_Y_POSITION, RANDOM.nextInt(16),
0, 0, 0, 0));
setPassengers = new FallbackPacketSnapshot(new SetPassengersPacket(VEHICLE_ENTITY_ID, PLAYER_ENTITY_ID));
// If the welcome message is empty, we don't need to send a message to the player
final String welcome = Sonar.get().getConfig().getMessagesConfig().getString("verification.welcome");
if (welcome.isEmpty()) {
welcomeMessage = null;
} else {
welcomeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
MiniMessage.miniMessage().deserialize(welcome,
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
}
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
@@ -211,9 +222,13 @@ public class FallbackPreparer {
}
// Update the CAPTCHA generator if necessary
if (Sonar.get().getFallback().getCaptchaGenerator() == null
|| Sonar.get().getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
final CaptchaGenerationStartEvent generationStartEvent = new CaptchaGenerationStartEvent(
Sonar.get().getFallback().getCaptchaGenerator());
Sonar.get().getEventManager().publish(generationStartEvent);
if (generationStartEvent.getCaptchaGenerator() == null
|| generationStartEvent.getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
} else {
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
@@ -229,8 +244,8 @@ public class FallbackPreparer {
Sonar.get().getFallback().setCaptchaGenerator(null);
}
maxTotalPacketsSent = maxMovementTick
+ (xpCountdown == null ? 0 : xpCountdown.length) * 3
maxTotalPacketsSent = maxMovementTick + 2
+ (xpCountdown == null ? 0 : xpCountdown.length) * 20 + 5
+ Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets() * 4
+ Sonar.get().getConfig().getVerification().getMap().getMaxTries()
+ 150 /* some arbitrary leeway */;
@@ -19,7 +19,6 @@ package xyz.jonesdev.sonar.common.fallback.protocol.block;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import java.util.function.Function;
@@ -27,6 +26,7 @@ import java.util.function.Function;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
// TODO: load mappings from a separate file
@Getter
@SuppressWarnings("unused")
@RequiredArgsConstructor
public enum BlockType {
@@ -67,7 +67,7 @@ public enum BlockType {
return 7385;
}
return 7389;
}, 0.75f),
}, protocolVersion -> (double) 0.75f),
TRAPDOOR(protocolVersion -> {
// We have to use wooden trapdoors for 1.7 since 1.7 doesn't have iron trapdoors
if (protocolVersion.compareTo(MINECRAFT_1_7_6) <= 0) {
@@ -107,7 +107,7 @@ public enum BlockType {
return 10273;
}
return 10414;
}, 0.1875),
}, protocolVersion -> 0.1875),
END_PORTAL_FRAME(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
return 120;
@@ -140,7 +140,7 @@ public enum BlockType {
return 7410;
}
return 7414;
}, 0.8125),
}, protocolVersion -> 0.8125),
DAYLIGHT_SENSOR(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
return 151;
@@ -176,7 +176,7 @@ public enum BlockType {
return 9067;
}
return 9207;
}, 0.375),
}, protocolVersion -> 0.375),
COBBLESTONE_WALL(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
return 139;
@@ -209,7 +209,7 @@ public enum BlockType {
return 7918;
}
return 7922;
}, 1.5),
}, protocolVersion -> 1.5),
STONE_SLABS(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
return 44;
@@ -245,7 +245,7 @@ public enum BlockType {
return 11090;
}
return 11231;
}, 0.5),
}, protocolVersion -> 0.5),
WHITE_CARPET(protocolVersion -> {
if (protocolVersion.compareTo(MINECRAFT_1_12_2) <= 0) {
return 171;
@@ -281,13 +281,8 @@ public enum BlockType {
return 10587;
}
return 10728;
}, 0.0625);
}, protocolVersion -> protocolVersion.compareTo(MINECRAFT_1_8) < 0 ? 0 : 0.0625);
private final Function<ProtocolVersion, Integer> idFunction;
@Getter
private final double blockHeight;
public int getId(final @NotNull ProtocolVersion protocolVersion) {
return idFunction.apply(protocolVersion);
}
private final Function<ProtocolVersion, Integer> id;
private final Function<ProtocolVersion, Double> blockHeight;
}
@@ -19,6 +19,7 @@ package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
import lombok.experimental.UtilityClass;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.event.impl.CaptchaGenerationEndEvent;
import xyz.jonesdev.sonar.api.timer.SystemTimer;
import java.awt.image.BufferedImage;
@@ -65,6 +66,7 @@ public class CaptchaPreparer {
}
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
Sonar.get().getEventManager().publish(new CaptchaGenerationEndEvent(timer, preparedAmount));
});
}
@@ -204,16 +204,21 @@ public class MapColorPalette {
closestIndex = index;
}
}
// Fix transparent colors by setting them to white
// TODO: implement better color tables (mappings?)
if (closestIndex > -1 && closestIndex < 3) {
closestIndex = 34;
}
return closestIndex;
}
private int calculateColorDistance(final int argb0, final int argb1) {
final int red0 = (argb0 >> 16) & 0xFF;
final int green0 = (argb0 >> 8) & 0xFF;
final int blue0 = argb0 & 0xFF;
final int red1 = (argb1 >> 16) & 0xFF;
final int green1 = (argb1 >> 8) & 0xFF;
final int blue1 = argb1 & 0xFF;
private int calculateColorDistance(final int rgb0, final int rgb1) {
final int red0 = (rgb0 >> 16) & 0xFF;
final int green0 = (rgb0 >> 8) & 0xFF;
final int blue0 = rgb0 & 0xFF;
final int red1 = (rgb1 >> 16) & 0xFF;
final int green1 = (rgb1 >> 8) & 0xFF;
final int blue1 = rgb1 & 0xFF;
// Color distance metric (e.g. squared Euclidean distance)
return (red0 - red1) * (red0 - red1)
+ (green0 - green1) * (green0 - green1)
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff. Show more