Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1671ccac55 | ||
|
|
8e03bdac5f | ||
|
|
7c0bdc568e | ||
|
|
354c7a0143 | ||
|
|
bb33aa3fa4 | ||
|
|
a0e308321d | ||
|
|
71b6d39eab | ||
|
|
3faf8c8b7f | ||
|
|
b45626eafb | ||
|
|
702ea1b370 | ||
|
|
a75c409e13 | ||
|
|
afe9f3f9ea | ||
|
|
ba63e80fd0 | ||
|
|
5ff7e23fcb | ||
|
|
cc194b0d62 | ||
|
|
4e4423e2ce | ||
|
|
ddb65f2f5a | ||
|
|
254faecc90 | ||
|
|
582024eba1 | ||
|
|
e1fe738ffe | ||
|
|
5f3a439113 | ||
|
|
27cca23412 | ||
|
|
2d2c576ad4 | ||
|
|
0adf7b0b6a | ||
|
|
c6ff9370c2 | ||
|
|
e33aac29c5 | ||
|
|
42dc3af6c9 | ||
|
|
ff402d5096 | ||
|
|
06a72bf3bd | ||
|
|
3fb4291163 | ||
|
|
cc57b1d978 | ||
|
|
750b42b5ab | ||
|
|
ad017024ae | ||
|
|
5887d82c19 | ||
|
|
b1e5d01062 | ||
|
|
a50bf45176 | ||
|
|
e0b5ad2a33 | ||
|
|
e5ee94362c | ||
|
|
d877568b38 | ||
|
|
0edec77135 | ||
|
|
54445cdfdb | ||
|
|
8545d60791 | ||
|
|
11327e2890 | ||
|
|
ad511836f3 | ||
|
|
5cb79d3acf | ||
|
|
032dd4f94e | ||
|
|
e831c1a646 | ||
|
|
f50c90a690 | ||
|
|
1e7e4ba5f1 |
No files matched your search
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@jonesdev.xyz.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
+26
-12
@@ -12,12 +12,9 @@ plugins {
|
||||
|
||||
apply(plugin = "io.toolebox.git-versioner")
|
||||
|
||||
rootProject.group = "xyz.jonesdev.sonar"
|
||||
val semanticVersion = "2.0.0"
|
||||
|
||||
versioner {
|
||||
pattern {
|
||||
pattern = "$semanticVersion-%h(-%c)"
|
||||
pattern = "$version-%h-%c-%b"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +32,24 @@ subprojects {
|
||||
apply(plugin = "com.github.johnrengelman.shadow")
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.projectlombok:lombok:1.18.28")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.28")
|
||||
compileOnly("org.projectlombok:lombok:1.18.30")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
testCompileOnly("org.projectlombok:lombok:1.18.28")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.28")
|
||||
testCompileOnly("org.projectlombok:lombok:1.18.30")
|
||||
testAnnotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
compileOnly("io.netty:netty-all:4.1.97.Final") // netty
|
||||
val adventureVersion = "4.14.0"
|
||||
|
||||
// adventure
|
||||
implementation("net.kyori:adventure-text-minimessage:$adventureVersion")
|
||||
implementation("net.kyori:adventure-text-serializer-gson:$adventureVersion")
|
||||
// adventure nbt
|
||||
implementation("net.kyori:adventure-nbt:$adventureVersion")
|
||||
|
||||
implementation("net.kyori:adventure-nbt:4.14.0") // nbt
|
||||
implementation("com.j256.ormlite:ormlite-jdbc:6.1") // ORM
|
||||
implementation("xyz.jonesdev:cappuccino:0.1.6-SNAPSHOT") // expiring cache
|
||||
|
||||
compileOnly("io.netty:netty-all:4.1.100.Final") // netty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +66,24 @@ tasks {
|
||||
// Set the implementation version, so we can create exact version
|
||||
// information in-game and make it accessible to the user.
|
||||
attributes["Implementation-Version"] = version
|
||||
attributes["Implementation-Vendor"] = "Jones Development, Sonar Contributors"
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// bStats has to be relocated to the Sonar package otherwise it throws an exception.
|
||||
relocate("org.bstats", "xyz.jonesdev.sonar.bstats")
|
||||
// Set the file name of the shadowed jar
|
||||
archiveFileName.set("${rootProject.name}.jar")
|
||||
|
||||
// bStats has to be relocated to the Sonar package otherwise it throws an exception
|
||||
// https://github.com/Bastian/bstats-metrics/blob/master/base/src/main/java/org/bstats/MetricsBase.java#L251
|
||||
relocate("org.bstats", "xyz.jonesdev.sonar.libs.bstats")
|
||||
|
||||
// https://github.com/jonesdevelopment/sonar/issues/46
|
||||
// Relocate some packages, so we don't run into issues where we accidentally use Velocity's classes
|
||||
relocate("net.kyori.adventure.nbt", "xyz.jonesdev.sonar.libs.nbt")
|
||||
relocate("com.google.gson", "xyz.jonesdev.sonar.libs.gson")
|
||||
relocate("com.j256.ormlite", "xyz.jonesdev.sonar.libs.ormlite")
|
||||
relocate("com.velocitypowered.natives", "xyz.jonesdev.sonar.libs.natives")
|
||||
}
|
||||
|
||||
compileJava {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.4
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
dependencies {
|
||||
implementation("com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4") // yaml config
|
||||
implementation("com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4") {
|
||||
exclude(group = "org.yaml")
|
||||
}
|
||||
|
||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEventManager;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
@@ -53,6 +54,12 @@ public interface Sonar {
|
||||
|
||||
@NotNull Verbose getVerboseHandler();
|
||||
|
||||
/**
|
||||
* Set a custom verbose handler
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setVerboseHandler(final @NotNull Verbose verboseHandler);
|
||||
|
||||
/**
|
||||
* Reloads the entire plugin
|
||||
*/
|
||||
@@ -63,10 +70,15 @@ public interface Sonar {
|
||||
return SonarVersion.GET;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default SonarEventManager getEventManager() {
|
||||
// We don't want anyone else to create a new instance.
|
||||
return SonarEventManager.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default Fallback getFallback() {
|
||||
// We only have one single instance of Fallback,
|
||||
// and we don't want anyone else to create a new instance.
|
||||
// We don't want anyone else to create a new instance.
|
||||
return Fallback.INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public final class SonarVersion {
|
||||
static final SonarVersion GET = new SonarVersion();
|
||||
private final String semanticVersion, full, formatted, commitSHA;
|
||||
private final int build;
|
||||
private final boolean isOnMainBranch;
|
||||
|
||||
SonarVersion() {
|
||||
final Package manifest = Sonar.class.getPackage();
|
||||
@@ -31,11 +32,12 @@ public final class SonarVersion {
|
||||
|
||||
if (versionString == null) {
|
||||
this.full = "<could not retrieve version>";
|
||||
this.semanticVersion = "<unknown>";
|
||||
this.commitSHA = "<unknown>";
|
||||
this.build = 0;
|
||||
this.semanticVersion = "<unknown version>";
|
||||
this.commitSHA = "<unknown commit>";
|
||||
this.build = -1;
|
||||
this.isOnMainBranch = false;
|
||||
this.formatted = full;
|
||||
Sonar.get().getLogger().error("Could not find version information (Is the manifest missing?)");
|
||||
Sonar.get().getLogger().warn("Could not find version information. Is the manifest missing?");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ public final class SonarVersion {
|
||||
this.semanticVersion = versionString.split("-")[0];
|
||||
this.commitSHA = versionString.split("-")[1];
|
||||
this.build = Integer.parseInt(versionString.split("-")[2]);
|
||||
this.isOnMainBranch = versionString.split("-")[3].equals("main");
|
||||
this.formatted = semanticVersion + " build " + build + " (" + commitSHA + ")";
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class CommandInvocation {
|
||||
private final InvocationSender sender;
|
||||
private final InvocationSource sender;
|
||||
private final Subcommand command;
|
||||
private final String[] rawArguments;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class InvocationSource {
|
||||
private final String name;
|
||||
private final Audience audience;
|
||||
|
||||
/**
|
||||
* Sends an empty chat message to the command executor
|
||||
*/
|
||||
public final void sendMessage() {
|
||||
sendMessage(Component.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*/
|
||||
public final void sendMessage(final String legacy) {
|
||||
sendMessage(MiniMessage.miniMessage().deserialize(legacy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
*/
|
||||
public final void sendMessage(final Component component) {
|
||||
audience.sendMessage(component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface SonarCommand {
|
||||
List<String> TAB_SUGGESTIONS = new ArrayList<>();
|
||||
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
|
||||
ExpiringCache<Object> DELAY = Cappuccino.buildExpiring(500L);
|
||||
|
||||
int COPYRIGHT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
|
||||
|
||||
List<Component> CACHED_HELP_MESSAGE = new Vector<>();
|
||||
|
||||
default void cacheHelpMessage() {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
Component.text("Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ ".", NamedTextColor.YELLOW),
|
||||
Component.text("(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors", NamedTextColor.YELLOW),
|
||||
Component.text("https://github.com/jonesdevelopment/sonar", NamedTextColor.GREEN)
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar")),
|
||||
Component.empty(),
|
||||
Component.text("Need help or have any questions?", NamedTextColor.YELLOW),
|
||||
Component.textOfChildren(
|
||||
Component.text("Open a ticket on the Discord ", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open Discord)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/")),
|
||||
Component.text("or open a new issue on GitHub.", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open GitHub)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"))
|
||||
),
|
||||
Component.empty()
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
Component component = Component.textOfChildren(
|
||||
Component.text(" ▪ ", NamedTextColor.GRAY),
|
||||
Component.text("/sonar " + sub.getInfo().name(), NamedTextColor.GREEN),
|
||||
Component.text(" - ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().description(), NamedTextColor.WHITE)
|
||||
);
|
||||
|
||||
Component hoverComponent = Component.textOfChildren(
|
||||
Component.text("Only players: ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().onlyPlayers() ? "✔" : "✗",
|
||||
sub.getInfo().onlyPlayers() ? NamedTextColor.GREEN : NamedTextColor.RED),
|
||||
Component.newline(),
|
||||
Component.text("Require console: ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().onlyConsole() ? "✔" : "✗",
|
||||
sub.getInfo().onlyConsole() ? NamedTextColor.GREEN : NamedTextColor.RED),
|
||||
Component.newline(),
|
||||
Component.text("Permission: ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getPermission(), NamedTextColor.WHITE)
|
||||
);
|
||||
if (sub.getInfo().aliases().length > 0) {
|
||||
hoverComponent = hoverComponent
|
||||
.append(Component.newline())
|
||||
.append(Component.text("Aliases: ", NamedTextColor.GRAY))
|
||||
.append(Component.text(sub.getAliases(), NamedTextColor.WHITE));
|
||||
}
|
||||
component = component
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND,
|
||||
"/sonar " + sub.getInfo().name() + " "))
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, hoverComponent));
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,14 @@ package xyz.jonesdev.sonar.api.command.subcommand;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -49,11 +52,30 @@ public abstract class Subcommand {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
protected final void incorrectUsage(final @NotNull InvocationSender sender) {
|
||||
sender.sendMessage(
|
||||
SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", info.name() + " (" + arguments + ")")
|
||||
);
|
||||
protected static @Nullable InetAddress checkIP(final InvocationSource source, final String rawIP) {
|
||||
if (!IP_PATTERN.matcher(rawIP).matches()) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIncorrectIpAddress());
|
||||
return null;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawIP);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIllegalIpAddress());
|
||||
return null;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getUnknownIpAddress());
|
||||
return null;
|
||||
}
|
||||
return inetAddress;
|
||||
}
|
||||
|
||||
protected final void incorrectUsage(final @NotNull InvocationSource sender) {
|
||||
sender.sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", info.name() + " (" + arguments + ")"));
|
||||
}
|
||||
|
||||
public abstract void execute(final @NotNull CommandInvocation invocation);
|
||||
|
||||
@@ -27,15 +27,19 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.LINE_SEPARATOR;
|
||||
|
||||
@Getter
|
||||
public final class SimpleYamlConfig {
|
||||
private final File file;
|
||||
private final YamlFile yaml;
|
||||
|
||||
private static final List<String> HEADER = Arrays.asList(
|
||||
"",
|
||||
"Sonar anti-bot version " + Sonar.get().getVersion(),
|
||||
" Currently running on: " + Sonar.get().getServer().getPlatform().getDisplayName(),
|
||||
" Need help? https://jonesdev.xyz/discord",
|
||||
"Running Sonar version " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName(),
|
||||
"Need help or have questions? https://jonesdev.xyz/discord",
|
||||
"https://github.com/jonesdevelopment/sonar",
|
||||
""
|
||||
);
|
||||
|
||||
@@ -48,6 +52,7 @@ public final class SimpleYamlConfig {
|
||||
throw new IllegalStateException("Could not create folder?!");
|
||||
}
|
||||
|
||||
this.file = file;
|
||||
this.yaml = new YamlFile(file.getPath());
|
||||
}
|
||||
|
||||
@@ -59,16 +64,18 @@ public final class SimpleYamlConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
try {
|
||||
yaml.setCommentFormat(YamlCommentFormat.PRETTY);
|
||||
public void load() throws Exception {
|
||||
// https://github.com/jonesdevelopment/sonar/issues/26
|
||||
// Only load the configuration if the file already exists
|
||||
if (yaml.exists()) {
|
||||
yaml.loadWithComments();
|
||||
} else {
|
||||
yaml.createOrLoadWithComments();
|
||||
|
||||
yaml.options().headerFormatter().commentPrefix("# ");
|
||||
yaml.setHeader(String.join(Sonar.LINE_SEPARATOR, HEADER));
|
||||
} catch (IOException exception) {
|
||||
exception.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
// Always load the comment format and options
|
||||
yaml.setCommentFormat(YamlCommentFormat.DEFAULT);
|
||||
yaml.setHeader(String.join(LINE_SEPARATOR, HEADER));
|
||||
}
|
||||
|
||||
public void set(final String path, final Object v) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+6
-8
@@ -45,16 +45,14 @@ public final class VerifiedPlayerController {
|
||||
private Dao<VerifiedPlayer, Integer> dao;
|
||||
private QueryBuilder<VerifiedPlayer, Integer> queryBuilder;
|
||||
@Getter
|
||||
private final @NotNull SonarConfiguration.DatabaseType cachedDatabaseType;
|
||||
private final @NotNull SonarConfiguration.Database.Type cachedDatabaseType;
|
||||
|
||||
public VerifiedPlayerController() {
|
||||
final SonarConfiguration config = Sonar.get().getConfig();
|
||||
|
||||
// Cache selected database type, so we don't need to call Sonar.get() every time
|
||||
cachedDatabaseType = config.DATABASE_TYPE;
|
||||
cachedDatabaseType = Sonar.get().getConfig().getDatabase().getType();
|
||||
|
||||
// Don't establish a database connection if the type is NONE
|
||||
if (cachedDatabaseType == SonarConfiguration.DatabaseType.NONE) {
|
||||
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
|
||||
Sonar.get().getLogger().warn("Make sure to configure a database to save verified players.");
|
||||
return;
|
||||
}
|
||||
@@ -89,7 +87,7 @@ public final class VerifiedPlayerController {
|
||||
_remove(inetAddress);
|
||||
|
||||
// Don't try to update the column if the database type is NONE
|
||||
if (cachedDatabaseType == SonarConfiguration.DatabaseType.NONE) {
|
||||
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,7 +129,7 @@ public final class VerifiedPlayerController {
|
||||
_add(player);
|
||||
|
||||
// Don't try to update the column if the database type is NONE
|
||||
if (cachedDatabaseType == SonarConfiguration.DatabaseType.NONE) {
|
||||
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,7 +179,7 @@ public final class VerifiedPlayerController {
|
||||
MAP.clear();
|
||||
|
||||
// Only update the column if the database type is not NONE
|
||||
if (cachedDatabaseType != SonarConfiguration.DatabaseType.NONE) {
|
||||
if (cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
|
||||
dao.deleteBuilder().delete();
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
|
||||
@@ -28,7 +28,8 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/Elytrium/LimboAuth/blob/master/src/main/java/net/elytrium/limboauth/dependencies/BaseLibrary.java
|
||||
@@ -77,13 +78,12 @@ public enum Dependency {
|
||||
|
||||
try (final InputStream inputStream = mvnRepoURL.openStream()) {
|
||||
Files.createDirectories(tempFilePath.getParent());
|
||||
Files.copy(inputStream, Files.createFile(tempFilePath), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(inputStream, Files.createFile(tempFilePath), REPLACE_EXISTING);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
|
||||
Sonar.get().getLogger().info("Finished downloading {} ({}s)!",
|
||||
tempFilePath.getFileName(), timer.formattedDelay());
|
||||
Sonar.get().getLogger().info("Finished downloading {} ({}s)!", tempFilePath.getFileName(), timer);
|
||||
}
|
||||
|
||||
return tempFilePath.toUri().toURL();
|
||||
|
||||
@@ -38,8 +38,8 @@ public class DependencyLoader {
|
||||
public ConnectionSource setUpDriverAndConnect() throws Throwable {
|
||||
final SonarConfiguration config = Sonar.get().getConfig();
|
||||
|
||||
final URL[] urls = new URL[config.DATABASE_TYPE.getDependencies().length];
|
||||
for (final Dependency dependency : config.DATABASE_TYPE.getDependencies()) {
|
||||
final URL[] urls = new URL[config.getDatabase().getType().getDependencies().length];
|
||||
for (final Dependency dependency : config.getDatabase().getType().getDependencies()) {
|
||||
final URL url = dependency.getClassLoaderURL();
|
||||
final ClassLoader currentClassLoader = DependencyLoader.class.getClassLoader();
|
||||
|
||||
@@ -50,12 +50,12 @@ public class DependencyLoader {
|
||||
urls[dependency.ordinal()] = url;
|
||||
}
|
||||
|
||||
final String type = config.DATABASE_TYPE.name().toLowerCase();
|
||||
final String databaseURL =
|
||||
"jdbc:" + type + "://" + config.SQL_URL + ":" + config.SQL_PORT + "/" + config.SQL_DATABASE;
|
||||
final String type = config.getDatabase().getType().name().toLowerCase();
|
||||
final String databaseURL = String.format("jdbc:%s://%s:%d/%s",
|
||||
type, config.getDatabase().getUrl(), config.getDatabase().getPort(), config.getDatabase().getName());
|
||||
|
||||
final ExternalClassLoader classLoader = new ExternalClassLoader(urls);
|
||||
final Connection connection = connect(classLoader, databaseURL, config);
|
||||
final Connection connection = connect(classLoader, databaseURL, config.getDatabase());
|
||||
return new JdbcSingleConnectionSource(databaseURL, connection);
|
||||
}
|
||||
|
||||
@@ -63,18 +63,16 @@ public class DependencyLoader {
|
||||
// https://github.com/Elytrium/LimboAuth/blob/master/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java#L134
|
||||
private Connection connect(final @NotNull ClassLoader classLoader,
|
||||
final @NotNull String databaseURL,
|
||||
final @NotNull SonarConfiguration configuration) throws Throwable {
|
||||
final Class<?> driverClass = classLoader.loadClass(configuration.DATABASE_TYPE.getDriverClassName());
|
||||
final @NotNull SonarConfiguration.Database config) throws Throwable {
|
||||
final Class<?> driverClass = classLoader.loadClass(config.getType().getDriverClassName());
|
||||
final Object driver = driverClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
DriverManager.registerDriver((Driver) driver);
|
||||
|
||||
final Properties properties = new Properties();
|
||||
if (!configuration.SQL_USER.isEmpty()) {
|
||||
properties.put("user", configuration.SQL_USER);
|
||||
}
|
||||
if (!configuration.SQL_PASSWORD.isEmpty()) {
|
||||
properties.put("password", configuration.SQL_PASSWORD);
|
||||
properties.put("user", config.getUsername());
|
||||
if (!config.getPassword().isEmpty()) {
|
||||
properties.put("password", config.getPassword());
|
||||
}
|
||||
|
||||
final Method connect = driverClass.getDeclaredMethod("connect", String.class, Properties.class);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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;
|
||||
|
||||
public interface SonarEvent {
|
||||
}
|
||||
+6
-17
@@ -15,26 +15,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
package xyz.jonesdev.sonar.api.event;
|
||||
|
||||
public interface InvocationSender {
|
||||
@FunctionalInterface
|
||||
public interface SonarEventListener {
|
||||
|
||||
/**
|
||||
* @return The name of the command executor
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Sends a message to the command executor
|
||||
* Handles the publishing of an event
|
||||
*
|
||||
* @param message Deserialized message
|
||||
* @param event [Cancellable] Sonar event
|
||||
*/
|
||||
void sendMessage(final String message);
|
||||
|
||||
/**
|
||||
* Sends an empty chat message to the command executor
|
||||
*/
|
||||
default void sendMessage() {
|
||||
sendMessage("");
|
||||
}
|
||||
void handle(final SonarEvent event);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Vector;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class SonarEventManager {
|
||||
public static final SonarEventManager INSTANCE = new SonarEventManager();
|
||||
private static final Collection<SonarEventListener> EVENT_LISTENERS = new Vector<>(0);
|
||||
|
||||
/**
|
||||
* Publishes the given event to all listeners
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public void publish(final @NotNull SonarEvent event) {
|
||||
for (final SonarEventListener eventListener : EVENT_LISTENERS) {
|
||||
eventListener.handle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers one (or more) event listeners
|
||||
*
|
||||
* @param listeners One (or more) listeners to register
|
||||
* @see #unregisterListener(SonarEventListener...) Unregister an event listener
|
||||
*/
|
||||
public synchronized void registerListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.addAll(Arrays.asList(listeners));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters one (or more) event listeners
|
||||
*
|
||||
* @param listeners One (or more) listeners to unregister
|
||||
* @apiNote This does not have any effect if the given listeners are not registered
|
||||
* @see #registerListener(SonarEventListener...) Register an event listener
|
||||
*/
|
||||
public synchronized void unregisterListener(final @NotNull SonarEventListener... listeners) {
|
||||
EVENT_LISTENERS.removeAll(Arrays.asList(listeners));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserBlacklistedEvent implements SonarEvent {
|
||||
@ApiStatus.Experimental
|
||||
private final FallbackUser<?, ?> user;
|
||||
}
|
||||
+14
-11
@@ -15,17 +15,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
package xyz.jonesdev.sonar.api.event.impl;
|
||||
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface SonarBaseCommand {
|
||||
List<String> TAB_SUGGESTIONS = new ArrayList<>();
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
ExpiringCache<Object> DELAY = Cappuccino.buildExpiring(500L);
|
||||
List<Object> CACHED_HELP_MESSAGE = new Vector<>();
|
||||
int COPYRIGHT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyFailedEvent implements SonarEvent {
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final String reason;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifyJoinEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final FallbackUser<?, ?> user;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.event.SonarEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class UserVerifySuccessEvent implements SonarEvent {
|
||||
private final String username;
|
||||
private final UUID offlineUuid;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final long timeTakenToVerify;
|
||||
}
|
||||
@@ -50,22 +50,22 @@ public final class Fallback {
|
||||
|
||||
@Override
|
||||
public void info(final String message, final Object... args) {
|
||||
SONAR.getLogger().info("[Fallback] " + message, args);
|
||||
SONAR.getLogger().info("[fallback] " + message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(final String message, final Object... args) {
|
||||
SONAR.getLogger().warn("[Fallback] " + message, args);
|
||||
SONAR.getLogger().warn("[fallback] " + message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(final String message, final Object... args) {
|
||||
SONAR.getLogger().error("[Fallback] " + message, args);
|
||||
SONAR.getLogger().error("[fallback] " + message, args);
|
||||
}
|
||||
};
|
||||
|
||||
public boolean isPotentiallyUnderAttack() {
|
||||
return connected.size() > SONAR.getConfig().MINIMUM_PLAYERS_FOR_ATTACK
|
||||
|| queue.getQueuedPlayers().size() > SONAR.getConfig().MINIMUM_PLAYERS_FOR_ATTACK;
|
||||
final int min = SONAR.getConfig().getMinPlayersForAttack();
|
||||
return connected.size() > min || queue.getQueuedPlayers().size() > min;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public final class FallbackQueue {
|
||||
}
|
||||
|
||||
public void poll() {
|
||||
final int max = Sonar.get().getConfig().MAXIMUM_QUEUE_POLLS;
|
||||
final int max = Sonar.get().getConfig().getQueue().getMaxQueuePolls();
|
||||
int index = 0;
|
||||
|
||||
// We need to be very careful here since we don't want any concurrency issues.
|
||||
|
||||
+32
-6
@@ -19,18 +19,21 @@ package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserBlacklistedEvent;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyFailedEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface FallbackPlayer<X, Y> {
|
||||
public interface FallbackUser<X, Y> {
|
||||
@NotNull Fallback getFallback();
|
||||
|
||||
@NotNull X getPlayer();
|
||||
@@ -49,9 +52,20 @@ public interface FallbackPlayer<X, Y> {
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
*
|
||||
* @param reason Disconnect message
|
||||
* @param reason Legacy disconnect message string
|
||||
* @see #disconnect(Component)
|
||||
*/
|
||||
void disconnect(final @NotNull String reason);
|
||||
default void disconnect(final @NotNull String reason) {
|
||||
disconnect(Component.text(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
*
|
||||
* @param reason Disconnect message component
|
||||
*/
|
||||
void disconnect(final @NotNull Component reason);
|
||||
|
||||
/**
|
||||
* Sends a packet/message to the player
|
||||
@@ -60,6 +74,12 @@ public interface FallbackPlayer<X, Y> {
|
||||
*/
|
||||
void write(final @NotNull Object msg);
|
||||
|
||||
/**
|
||||
* Queues a buffered message that will be
|
||||
* sent once all messages are flushed.
|
||||
*/
|
||||
void delayedWrite(final @NotNull Object msg);
|
||||
|
||||
ExpiringCache<String> PREVIOUS_FAILS = Cappuccino.buildExpiring(3L, TimeUnit.MINUTES);
|
||||
|
||||
/**
|
||||
@@ -72,10 +92,13 @@ public interface FallbackPlayer<X, Y> {
|
||||
*/
|
||||
default void fail(final @Nullable String reason) {
|
||||
if (getChannel().isActive()) {
|
||||
disconnect(Sonar.get().getConfig().VERIFICATION_FAILED);
|
||||
// Call the VerifyFailedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(this, reason));
|
||||
|
||||
disconnect(Sonar.get().getConfig().getVerification().getVerificationFailed());
|
||||
|
||||
if (reason != null) {
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().VERIFICATION_FAILED_LOG
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getFailedLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol()))
|
||||
.replace("%reason%", reason));
|
||||
@@ -89,8 +112,11 @@ public interface FallbackPlayer<X, Y> {
|
||||
|
||||
// Check if the player has too many failed attempts
|
||||
if (PREVIOUS_FAILS.has(getInetAddress().toString())) {
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
|
||||
getFallback().getBlacklisted().put(getInetAddress().toString());
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().VERIFICATION_BLACKLIST_LOG
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
} else {
|
||||
+3
-7
@@ -17,6 +17,7 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.fallback.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@@ -66,10 +67,10 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_19_3(761),
|
||||
MINECRAFT_1_19_4(762),
|
||||
MINECRAFT_1_20(763),
|
||||
MINECRAFT_1_20_2(-1);
|
||||
MINECRAFT_1_20_2(764);
|
||||
|
||||
@Getter
|
||||
private final int protocol;
|
||||
private final int snapshotProtocol = -1;
|
||||
|
||||
public static final ProtocolVersion MINIMUM_VERSION = MINECRAFT_1_7_2;
|
||||
public static final Map<Integer, ProtocolVersion> ID_TO_PROTOCOL_CONSTANT;
|
||||
@@ -81,7 +82,6 @@ public enum ProtocolVersion {
|
||||
|
||||
for (final ProtocolVersion version : values()) {
|
||||
versions.putIfAbsent(version.protocol, version);
|
||||
versions.put(version.snapshotProtocol, version);
|
||||
}
|
||||
|
||||
ID_TO_PROTOCOL_CONSTANT = versions;
|
||||
@@ -104,10 +104,6 @@ public enum ProtocolVersion {
|
||||
return ID_TO_PROTOCOL_CONSTANT.getOrDefault(protocol, UNKNOWN);
|
||||
}
|
||||
|
||||
public int getProtocol() {
|
||||
return protocol == -1 ? snapshotProtocol : protocol;
|
||||
}
|
||||
|
||||
public boolean isUnknown() {
|
||||
return this == UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -17,22 +17,26 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.server;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ServerWrapper {
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class ServerWrapper {
|
||||
|
||||
/**
|
||||
* @return Platform of the server (Velocity, BungeeCord, Spigot)
|
||||
* Platform of the server (Velocity, BungeeCord, Spigot)
|
||||
*/
|
||||
SonarPlatform getPlatform();
|
||||
private final SonarPlatform platform;
|
||||
|
||||
/**
|
||||
* @param username Username of the player
|
||||
* @return Optional player wrapped as InvocationSender
|
||||
* @see xyz.jonesdev.sonar.api.command.InvocationSender
|
||||
* @see InvocationSource
|
||||
*/
|
||||
Optional<InvocationSender> getOnlinePlayer(final String username);
|
||||
public abstract Optional<InvocationSource> getOnlinePlayer(final String username);
|
||||
}
|
||||
@@ -35,7 +35,8 @@ public final class SystemTimer {
|
||||
return delay() >= amount;
|
||||
}
|
||||
|
||||
public String formattedDelay() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%.3f", delay() / 1000D);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package xyz.jonesdev.sonar.api.verbose;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.profiler.JVMProfiler;
|
||||
@@ -47,7 +49,7 @@ public abstract class Verbose implements JVMProfiler {
|
||||
totalJoins = Statistics.TOTAL_TRAFFIC.get();
|
||||
|
||||
// Statistically determine the joins per second without any caches
|
||||
if (totalJoins > 0 && secondTimer.delay() >= 1000L) {
|
||||
if (totalJoins > 0 && secondTimer.elapsed(1000L)) {
|
||||
secondTimer.reset();
|
||||
joinsPerSecond = totalJoins - lastTotalJoins;
|
||||
lastTotalJoins = totalJoins;
|
||||
@@ -57,8 +59,8 @@ public abstract class Verbose implements JVMProfiler {
|
||||
broadcast(prepareActionBarFormat());
|
||||
}
|
||||
|
||||
protected final @NotNull String prepareActionBarFormat() {
|
||||
return Sonar.get().getConfig().ACTION_BAR_LAYOUT
|
||||
protected @NotNull Component prepareActionBarFormat() {
|
||||
return MiniMessage.miniMessage().deserialize(Sonar.get().getConfig().getVerbose().getActionBarLayout()
|
||||
.replace("%queued%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
|
||||
@@ -78,37 +80,37 @@ public abstract class Verbose implements JVMProfiler {
|
||||
.replace("%free-memory%", formatMemory(getFreeMemory()))
|
||||
.replace("%total-memory%", formatMemory(getTotalMemory()))
|
||||
.replace("%max-memory%", formatMemory(getMaxMemory()))
|
||||
.replace("%animation%", nextAnimation());
|
||||
.replace("%animation%", nextAnimation()));
|
||||
}
|
||||
|
||||
protected final String nextAnimation() {
|
||||
val animations = Sonar.get().getConfig().ANIMATION;
|
||||
val animations = Sonar.get().getConfig().getVerbose().getAnimation();
|
||||
final int nextIndex = ++animationIndex % animations.size();
|
||||
return animations.toArray(new String[0])[nextIndex];
|
||||
}
|
||||
|
||||
// Run action bar verbose
|
||||
protected abstract void broadcast(final String message);
|
||||
protected abstract void broadcast(final Component component);
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player who subscribed
|
||||
* @return Whether the player is subscribed or not
|
||||
*/
|
||||
public boolean isSubscribed(final @NotNull String subscriber) {
|
||||
public final boolean isSubscribed(final @NotNull String subscriber) {
|
||||
return subscribers.contains(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player to subscribe
|
||||
*/
|
||||
public void subscribe(final @NotNull String subscriber) {
|
||||
public final void subscribe(final @NotNull String subscriber) {
|
||||
subscribers.add(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriber Name of the player to unsubscribe
|
||||
*/
|
||||
public void unsubscribe(final @NotNull String subscriber) {
|
||||
public final void unsubscribe(final @NotNull String subscriber) {
|
||||
subscribers.remove(subscriber);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ apply(plugin = "net.minecrell.plugin-yml.bukkit")
|
||||
|
||||
bukkit {
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString()
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
website = "https://jonesdev.xyz/discord/"
|
||||
@@ -17,7 +17,7 @@ bukkit {
|
||||
|
||||
commands {
|
||||
register("sonar") {
|
||||
permission = "sonar.command"
|
||||
// No permission check here
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ dependencies {
|
||||
compileOnly(project(":api"))
|
||||
compileOnly(project(":common"))
|
||||
|
||||
// MiniMessage platform support
|
||||
implementation("net.kyori:adventure-platform-bukkit:4.3.1")
|
||||
|
||||
// We have to use 1.8 for backwards compatibility
|
||||
compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT")
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ import lombok.Getter;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.bukkit.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.command.BukkitInvocationSource;
|
||||
import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
|
||||
import xyz.jonesdev.sonar.bukkit.verbose.ActionBarVerbose;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
|
||||
@@ -72,44 +73,25 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUKKIT) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.VELOCITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getOnlinePlayers().stream()
|
||||
.filter(player -> player.getName().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
});
|
||||
.map(BukkitInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
// Initialize bStats.org metrics
|
||||
new Metrics(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new SonarCommand());
|
||||
Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand());
|
||||
|
||||
// Register Fallback queue task
|
||||
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(), getFallback().getQueue()::poll,
|
||||
@@ -123,9 +105,4 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(), getVerboseHandler()::update,
|
||||
2L, 2L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postReload() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bukkit.command;
|
||||
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.bukkit.SonarBukkit;
|
||||
|
||||
public final class BukkitInvocationSource extends InvocationSource {
|
||||
private static final BukkitAudiences AUDIENCES = BukkitAudiences.create(SonarBukkit.INSTANCE.getPlugin());
|
||||
|
||||
public BukkitInvocationSource(final @NotNull CommandSender sender) {
|
||||
super(sender.getName(), AUDIENCES.sender(sender));
|
||||
}
|
||||
}
|
||||
+30
-98
@@ -17,18 +17,14 @@
|
||||
|
||||
package xyz.jonesdev.sonar.bukkit.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -39,62 +35,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBaseCommand {
|
||||
private static final TextComponent GITHUB_LINK_COMPONENT = new TextComponent(ChatColor.GREEN + "https://github" +
|
||||
".com/jonesdevelopment/sonar");
|
||||
private static final TextComponent DISCORD_SUPPORT = new TextComponent(ChatColor.YELLOW + "Open a ticket on the " +
|
||||
"Discord ");
|
||||
private static final TextComponent GITHUB_ISSUES = new TextComponent(ChatColor.YELLOW + "or open a new issue on " +
|
||||
"GitHub.");
|
||||
|
||||
static {
|
||||
GITHUB_LINK_COMPONENT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github" +
|
||||
".com/jonesdevelopment/sonar"));
|
||||
DISCORD_SUPPORT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/"));
|
||||
DISCORD_SUPPORT.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"Discord)").create()));
|
||||
GITHUB_ISSUES.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"));
|
||||
GITHUB_ISSUES.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"GitHub)").create()));
|
||||
}
|
||||
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
new TextComponent(ChatColor.YELLOW + "Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ "."),
|
||||
new TextComponent(ChatColor.YELLOW + "(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors"),
|
||||
GITHUB_LINK_COMPONENT,
|
||||
new TextComponent(""),
|
||||
new TextComponent(ChatColor.YELLOW + "Need help or have any questions?"),
|
||||
new TextComponent(
|
||||
DISCORD_SUPPORT,
|
||||
GITHUB_ISSUES
|
||||
),
|
||||
new TextComponent("")
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
final TextComponent component = new TextComponent(
|
||||
new TextComponent(ChatColor.GRAY + " ▪ "),
|
||||
new TextComponent(ChatColor.GREEN + "/sonar " + sub.getInfo().name()),
|
||||
new TextComponent(ChatColor.GRAY + " - "),
|
||||
new TextComponent(ChatColor.WHITE + sub.getInfo().description())
|
||||
);
|
||||
|
||||
component.setClickEvent(
|
||||
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
);
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
).create())
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, SonarCommand {
|
||||
{
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +45,15 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
final Command command,
|
||||
final String label,
|
||||
final String[] args) {
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new BukkitInvocationSource(sender);
|
||||
|
||||
if (!(sender instanceof ConsoleCommandSender)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return false;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -112,16 +63,14 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
sender.sendMessage(Sonar.get().getConfig().COMMAND_COOL_DOWN);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
sender.sendMessage(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -130,19 +79,6 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return sender.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
if (args.length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream()
|
||||
@@ -156,10 +92,8 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !sender.hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -167,24 +101,22 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(sender instanceof Player)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, args);
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args);
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,12 +129,8 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
if (sender instanceof Player) {
|
||||
((Player) sender).spigot().sendMessage((TextComponent) component);
|
||||
} else {
|
||||
sender.sendMessage(((TextComponent) component).getText());
|
||||
}
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -213,6 +141,10 @@ public final class SonarCommand implements CommandExecutor, TabExecutor, SonarBa
|
||||
final Command command,
|
||||
final String commandAlias,
|
||||
final String @NotNull [] args) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (args.length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
@@ -18,7 +18,7 @@
|
||||
package xyz.jonesdev.sonar.bukkit.verbose;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
@@ -27,14 +27,13 @@ import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
public final class ActionBarVerbose extends Verbose {
|
||||
private final Server server;
|
||||
|
||||
public void broadcast(final String message) {
|
||||
final TextComponent component = new TextComponent(message);
|
||||
|
||||
@Override
|
||||
public void broadcast(final Component component) {
|
||||
synchronized (subscribers) {
|
||||
for (final String subscriber : subscribers) {
|
||||
final Player player = server.getPlayer(subscriber);
|
||||
if (player != null) {
|
||||
//player.sendMessage(ChatMessageType.ACTION_BAR, component);
|
||||
// TODO: use an adventure audience
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ apply(plugin = "net.minecrell.plugin-yml.bungee")
|
||||
|
||||
bungee {
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
|
||||
author = "Jones Development, Sonar Contributors"
|
||||
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
|
||||
}
|
||||
@@ -24,6 +24,9 @@ dependencies {
|
||||
|
||||
compileOnly("net.md_5:bungeecord:1.20.2-rc2-SNAPSHOT")
|
||||
|
||||
// MiniMessage platform support
|
||||
implementation("net.kyori:adventure-platform-bungeecord:4.3.1")
|
||||
|
||||
// Implement bStats.org for metrics
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.2")
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
package xyz.jonesdev.sonar.bungee;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.bungee.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.command.BungeeInvocationSource;
|
||||
import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackListener;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.injection.BaseInjectionHelper;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.injection.ChildChannelInitializer;
|
||||
@@ -76,44 +76,25 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUNGEE) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.BUNGEE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getPlayers().stream()
|
||||
.filter(player -> player.getName().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(new TextComponent(message));
|
||||
}
|
||||
});
|
||||
.map(BungeeInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
// Initialize bStats.org metrics
|
||||
new Metrics(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new SonarCommand());
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getPluginManager().registerListener(getPlugin(), new FallbackListener(getFallback()));
|
||||
@@ -133,11 +114,4 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
// Inject base into ProtocolUtils
|
||||
BaseInjectionHelper.inject(ChildChannelInitializer.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postReload() {
|
||||
|
||||
// Prepare cached messages
|
||||
FallbackListener.CachedMessages.update();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bungee.command;
|
||||
|
||||
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
|
||||
public final class BungeeInvocationSource extends InvocationSource {
|
||||
private static final BungeeAudiences AUDIENCES = BungeeAudiences.create(SonarBungee.INSTANCE.getPlugin());
|
||||
|
||||
public BungeeInvocationSource(final @NotNull CommandSender sender) {
|
||||
super(sender.getName(), AUDIENCES.sender(sender));
|
||||
}
|
||||
}
|
||||
+31
-98
@@ -17,12 +17,8 @@
|
||||
|
||||
package xyz.jonesdev.sonar.bungee.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
@@ -30,8 +26,8 @@ import net.md_5.bungee.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -42,72 +38,24 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand extends Command implements TabExecutor, SonarBaseCommand {
|
||||
public SonarCommand() {
|
||||
super("sonar", "sonar.command");
|
||||
}
|
||||
|
||||
private static final TextComponent GITHUB_LINK_COMPONENT = new TextComponent(ChatColor.GREEN + "https://github" +
|
||||
".com/jonesdevelopment/sonar");
|
||||
private static final TextComponent DISCORD_SUPPORT = new TextComponent(ChatColor.YELLOW + "Open a ticket on the " +
|
||||
"Discord ");
|
||||
private static final TextComponent GITHUB_ISSUES = new TextComponent(ChatColor.YELLOW + "or open a new issue on " +
|
||||
"GitHub.");
|
||||
|
||||
static {
|
||||
GITHUB_LINK_COMPONENT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github" +
|
||||
".com/jonesdevelopment/sonar"));
|
||||
DISCORD_SUPPORT.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/"));
|
||||
DISCORD_SUPPORT.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"Discord)").create()));
|
||||
GITHUB_ISSUES.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"));
|
||||
GITHUB_ISSUES.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("(Click to open " +
|
||||
"GitHub)").create()));
|
||||
}
|
||||
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
new TextComponent(ChatColor.YELLOW + "Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ "."),
|
||||
new TextComponent(ChatColor.YELLOW + "(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors"),
|
||||
GITHUB_LINK_COMPONENT,
|
||||
new TextComponent(""),
|
||||
new TextComponent(ChatColor.YELLOW + "Need help or have any questions?"),
|
||||
new TextComponent(
|
||||
DISCORD_SUPPORT,
|
||||
GITHUB_ISSUES
|
||||
),
|
||||
new TextComponent("")
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
final TextComponent component = new TextComponent(
|
||||
new TextComponent(ChatColor.GRAY + " ▪ "),
|
||||
new TextComponent(ChatColor.GREEN + "/sonar " + sub.getInfo().name()),
|
||||
new TextComponent(ChatColor.GRAY + " - "),
|
||||
new TextComponent(ChatColor.WHITE + sub.getInfo().description())
|
||||
);
|
||||
|
||||
component.setClickEvent(
|
||||
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
);
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
).create())
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class BungeeSonarCommand extends Command implements TabExecutor, SonarCommand {
|
||||
public BungeeSonarCommand() {
|
||||
super("sonar");
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"redundantSuppression"})
|
||||
public void execute(final @NotNull CommandSender sender, final String[] args) {
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new BungeeInvocationSource(sender);
|
||||
|
||||
if (!(sender instanceof ConsoleCommandSender)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -117,16 +65,14 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
sender.sendMessage(new TextComponent(Sonar.get().getConfig().COMMAND_COOL_DOWN));
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
sender.sendMessage(new TextComponent(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
));
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,19 +81,6 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return sender.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
sender.sendMessage(new TextComponent(message));
|
||||
}
|
||||
};
|
||||
|
||||
if (args.length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream()
|
||||
@@ -161,10 +94,8 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !sender.hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -172,24 +103,22 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(sender instanceof ProxiedPlayer)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, args);
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args);
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,14 +131,18 @@ public final class SonarCommand extends Command implements TabExecutor, SonarBas
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
sender.sendMessage((TextComponent) component);
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> onTabComplete(final CommandSender sender, final String @NotNull [] args) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!sender.hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (args.length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
+28
-25
@@ -21,8 +21,11 @@ import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.compress.PacketCompressor;
|
||||
@@ -30,17 +33,18 @@ import net.md_5.bungee.compress.PacketDecompressor;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.packet.Kick;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.compress.FallbackPacketCompressor;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.compress.FallbackPacketDecompressor;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.handler.FallbackInitialHandler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.FRAME_PREPENDER;
|
||||
import static xyz.jonesdev.sonar.bungee.fallback.FallbackListener.CachedMessages.LOCKDOWN_DISCONNECT;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackListener implements Listener {
|
||||
@@ -57,36 +61,35 @@ public final class FallbackListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public static class CachedMessages {
|
||||
static TextComponent LOCKDOWN_DISCONNECT;
|
||||
|
||||
public static void update() {
|
||||
LOCKDOWN_DISCONNECT = new TextComponent(Sonar.get().getConfig().LOCKDOWN_DISCONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@SuppressWarnings("deprecation")
|
||||
public void handle(final @NotNull PostLoginEvent event) throws Throwable {
|
||||
if (Sonar.get().getConfig().LOCKDOWN_ENABLED) {
|
||||
if (!event.getPlayer().hasPermission("sonar.lockdown")) {
|
||||
event.getPlayer().disconnect(LOCKDOWN_DISCONNECT);
|
||||
if (Sonar.get().getConfig().getLockdown().isEnabled()) {
|
||||
if (!event.getPlayer().hasPermission(Sonar.get().getConfig().getLockdown().getBypassPermission())) {
|
||||
final PendingConnection pendingConnection = event.getPlayer().getPendingConnection();
|
||||
// Try to close the channel with a custom serialized disconnect component
|
||||
if (pendingConnection instanceof FallbackInitialHandler) {
|
||||
final FallbackInitialHandler fallbackInitialHandler = (FallbackInitialHandler) pendingConnection;
|
||||
final Component component = Sonar.get().getConfig().getLockdown().getDisconnect();
|
||||
final String serialized = JSONComponentSerializer.json().serialize(component);
|
||||
fallbackInitialHandler.closeWith(new Kick(serialized));
|
||||
} else {
|
||||
// Fallback by disconnecting without a message
|
||||
pendingConnection.disconnect();
|
||||
Sonar.get().getLogger().warn("Fallback handler of {} is missing", event.getPlayer().getName());
|
||||
}
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_LOG_ATTEMPTS) {
|
||||
Sonar.get().getLogger().info(
|
||||
Sonar.get().getConfig().LOCKDOWN_CONSOLE_LOG
|
||||
.replace("%player%", event.getPlayer().getName())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getPendingConnection().getVersion()))
|
||||
);
|
||||
if (Sonar.get().getConfig().getLockdown().isLogAttempts()) {
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getLockdown().getConsoleLog()
|
||||
.replace("%player%", event.getPlayer().getName())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getPendingConnection().getVersion())));
|
||||
}
|
||||
return;
|
||||
} else if (Sonar.get().getConfig().LOCKDOWN_ENABLE_NOTIFY) {
|
||||
event.getPlayer().sendMessage(
|
||||
new TextComponent(Sonar.get().getConfig().LOCKDOWN_NOTIFICATION)
|
||||
);
|
||||
} else if (Sonar.get().getConfig().getLockdown().isNotifyAdmins()) {
|
||||
event.getPlayer().sendMessage(new TextComponent(Sonar.get().getConfig().getLockdown().getNotification()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-6
@@ -22,20 +22,20 @@ import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.handler.FallbackInitialHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.Disconnect;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.Disconnect;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPlayerWrapper implements FallbackPlayer<ChannelWrapper, FallbackInitialHandler> {
|
||||
public final class FallbackUserWrapper implements FallbackUser<ChannelWrapper, FallbackInitialHandler> {
|
||||
private final Fallback fallback;
|
||||
private final ChannelWrapper player;
|
||||
private final FallbackInitialHandler connection;
|
||||
@@ -45,8 +45,8 @@ public final class FallbackPlayerWrapper implements FallbackPlayer<ChannelWrappe
|
||||
private final ProtocolVersion protocolVersion;
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull String reason) {
|
||||
connection.closeWith(Disconnect.create(TextComponent.toLegacyText(new TextComponent(reason))));
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,4 +57,13 @@ public final class FallbackPlayerWrapper implements FallbackPlayer<ChannelWrappe
|
||||
ReferenceCountUtil.release(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedWrite(final @NotNull Object packet) {
|
||||
if (channel.isActive()) {
|
||||
channel.write(packet, channel.voidPromise());
|
||||
} else {
|
||||
ReferenceCountUtil.release(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackPlayerWrapper;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackUserWrapper;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
@@ -43,7 +43,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
private static final @NotNull Fallback FALLBACK = Objects.requireNonNull(Sonar.get().getFallback());
|
||||
private ChannelWrapper channelWrapper;
|
||||
@SuppressWarnings("unused") // TODO: remove later
|
||||
private @Nullable FallbackPlayerWrapper player;
|
||||
private @Nullable FallbackUserWrapper player;
|
||||
|
||||
@Override
|
||||
public void connected(final ChannelWrapper channelWrapper) throws Exception {
|
||||
@@ -58,7 +58,7 @@ public final class FallbackInitialHandler extends InitialHandler {
|
||||
if (player == null && !Sonar.get().getVerifiedPlayerController().has(inetAddress)) {
|
||||
final Channel channel = channelWrapper.getHandle();
|
||||
|
||||
player = new FallbackPlayerWrapper(
|
||||
player = new FallbackUserWrapper(
|
||||
FALLBACK, channelWrapper, this,
|
||||
channel, channel.pipeline(), inetAddress,
|
||||
ProtocolVersion.fromId(getHandshake().getProtocolVersion())
|
||||
|
||||
+2
-6
@@ -27,13 +27,10 @@ import net.md_5.bungee.netty.PipelineUtils;
|
||||
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.varint.Varint21FrameDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackTimeoutHandler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
@@ -42,8 +39,7 @@ import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
public final class BaseChannelInitializer extends ChannelInitializer<Channel> {
|
||||
public static final BaseChannelInitializer INSTANCE = new BaseChannelInitializer();
|
||||
|
||||
// cached object of our configuration to instantly access it
|
||||
private static final SonarConfiguration CONFIG = Objects.requireNonNull(Sonar.get().getConfig());
|
||||
private static final boolean REPLACE_VAR_INT_DECODER = !Boolean.getBoolean("sonar.do-not-replace-decoder");
|
||||
|
||||
private static final WriteBufferWaterMark MARK;
|
||||
private static final Varint21LengthFieldPrepender FRAME_ENCODER;
|
||||
@@ -75,7 +71,7 @@ public final class BaseChannelInitializer extends ChannelInitializer<Channel> {
|
||||
channel.config().setAllocator(PooledByteBufAllocator.DEFAULT);
|
||||
channel.config().setWriteBufferWaterMark(MARK);
|
||||
|
||||
channel.pipeline().addLast(FRAME_DECODER, CONFIG.REPLACE_VAR_INT_DECODER
|
||||
channel.pipeline().addLast(FRAME_DECODER, REPLACE_VAR_INT_DECODER
|
||||
? new Varint21FrameDecoder() : new net.md_5.bungee.protocol.Varint21FrameDecoder());
|
||||
channel.pipeline().addLast(TIMEOUT_HANDLER, new FallbackTimeoutHandler(
|
||||
BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS
|
||||
|
||||
@@ -18,9 +18,8 @@
|
||||
package xyz.jonesdev.sonar.bungee.verbose;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
|
||||
@@ -28,14 +27,13 @@ import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
public final class VerboseWrapper extends Verbose {
|
||||
private final ProxyServer server;
|
||||
|
||||
public void broadcast(final String message) {
|
||||
final TextComponent component = new TextComponent(message);
|
||||
|
||||
@Override
|
||||
public void broadcast(final Component component) {
|
||||
synchronized (subscribers) {
|
||||
for (final String subscriber : subscribers) {
|
||||
final ProxiedPlayer player = server.getPlayer(subscriber);
|
||||
if (player != null) {
|
||||
player.sendMessage(ChatMessageType.ACTION_BAR, component);
|
||||
// TODO: use an adventure audience
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package xyz.jonesdev.sonar.common.boot;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
@@ -32,6 +33,7 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.subcommand.SubcommandRegistryHolder;
|
||||
import xyz.jonesdev.sonar.common.update.UpdateChecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
@@ -41,6 +43,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequiredArgsConstructor
|
||||
public abstract class SonarBootstrap<T> implements Sonar {
|
||||
private T plugin;
|
||||
@Setter
|
||||
private Verbose verboseHandler;
|
||||
private SonarConfiguration config;
|
||||
private VerifiedPlayerController verifiedPlayerController;
|
||||
@@ -63,13 +66,20 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
}
|
||||
|
||||
public final void initialize() {
|
||||
getLogger().info("Successfully booted in {}s!", launchTimer.formattedDelay());
|
||||
// Check if the branch is not the main branch to warn about unstable versions
|
||||
if (!getVersion().isOnMainBranch()) {
|
||||
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...");
|
||||
|
||||
// Reload configuration
|
||||
reload();
|
||||
|
||||
getLogger().info("Successfully initialized components in {}s!", launchTimer.formattedDelay());
|
||||
getLogger().info("Successfully initialized components in {}s!", launchTimer);
|
||||
getLogger().info("Enabling all tasks and features...");
|
||||
|
||||
try {
|
||||
@@ -77,11 +87,16 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
enable();
|
||||
|
||||
// Done
|
||||
getLogger().info("Done ({}s)!", launchTimer.formattedDelay());
|
||||
getLogger().info("Done ({}s)!", launchTimer);
|
||||
} catch (Throwable throwable) {
|
||||
// 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
|
||||
UpdateChecker.checkForUpdates();
|
||||
}
|
||||
|
||||
public abstract void enable();
|
||||
@@ -92,7 +107,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
|
||||
// Warn player if they reloaded and changed the database type
|
||||
if (getVerifiedPlayerController() != null
|
||||
&& getVerifiedPlayerController().getCachedDatabaseType() != getConfig().DATABASE_TYPE) {
|
||||
&& getVerifiedPlayerController().getCachedDatabaseType() != getConfig().getDatabase().getType()) {
|
||||
Sonar.get().getLogger().warn("Reloading the server after changing the database type"
|
||||
+ " is generally not recommended as it can sometimes cause data loss.");
|
||||
}
|
||||
@@ -102,19 +117,13 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
|
||||
// Update ratelimiter
|
||||
final ExpiringCache<InetAddress> expiringCache = Cappuccino.buildExpiring(
|
||||
getConfig().VERIFICATION_DELAY, TimeUnit.MILLISECONDS, 250L
|
||||
);
|
||||
getConfig().getVerification().getReconnectDelay(), TimeUnit.MILLISECONDS, 250L);
|
||||
FallbackRatelimiter.INSTANCE.setExpiringCache(expiringCache);
|
||||
|
||||
// Reinitialize database controller
|
||||
verifiedPlayerController = new VerifiedPlayerController();
|
||||
|
||||
// Call post reload task
|
||||
postReload();
|
||||
}
|
||||
|
||||
public abstract void postReload();
|
||||
|
||||
public void shutdown() {
|
||||
getLogger().info("Starting shutdown process...");
|
||||
// ...
|
||||
|
||||
+288
-152
@@ -20,77 +20,111 @@ package xyz.jonesdev.sonar.common.fallback;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.val;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketListener;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.*;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.*;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledged;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
import xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_13;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_ENCODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackVerificationHandler implements FallbackPacketListener {
|
||||
private final @NotNull FallbackPlayer<?, ?> player;
|
||||
private final @NotNull FallbackUser<?, ?> user;
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final short transactionId;
|
||||
private final long verifyKeepAliveId;
|
||||
private int movementTick, receivedPackets;
|
||||
private double lastX, lastY, lastZ;
|
||||
private final UUID playerUuid;
|
||||
private short expectedTransactionId;
|
||||
private long expectedKeepAliveId;
|
||||
private int expectedTeleportId = -1;
|
||||
private int tick, totalReceivedPackets;
|
||||
private int ignoredMovementTicks;
|
||||
private double posX, posY, posZ, lastY;
|
||||
private boolean resolvedClientBrand, resolvedClientSettings;
|
||||
@Setter
|
||||
private State state;
|
||||
private @NotNull State state = State.LOGIN_ACK;
|
||||
private boolean listenForMovements;
|
||||
|
||||
private final SystemTimer login = new SystemTimer();
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum State {
|
||||
// LOGIN
|
||||
KEEP_ALIVE(false),
|
||||
// PLAY
|
||||
CLIENT_SETTINGS(false),
|
||||
PLUGIN_MESSAGE(false),
|
||||
TRANSACTION(false),
|
||||
// IN-GAME
|
||||
TELEPORT(false),
|
||||
POSITION(true),
|
||||
COLLISIONS(true),
|
||||
// OTHER
|
||||
SUCCESS(false);
|
||||
|
||||
private final boolean canMove;
|
||||
// 1.20.2 configuration state
|
||||
LOGIN_ACK, CONFIGURE,
|
||||
// pre-JOIN ping check
|
||||
KEEP_ALIVE,
|
||||
// post-JOIN checks
|
||||
CLIENT_SETTINGS, PLUGIN_MESSAGE, TRANSACTION,
|
||||
// PLAY checks
|
||||
TELEPORT, POSITION, COLLISIONS,
|
||||
// Done
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
public FallbackVerificationHandler(final @NotNull FallbackPlayer<?, ?> player,
|
||||
public FallbackVerificationHandler(final @NotNull FallbackUser<?, ?> user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
this.player = player;
|
||||
final @NotNull UUID playerUuid) {
|
||||
this.user = user;
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.transactionId = (short) RANDOM.nextInt();
|
||||
this.verifyKeepAliveId = RANDOM.nextInt();
|
||||
this.state = State.KEEP_ALIVE;
|
||||
this.playerUuid = playerUuid;
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
player.write(new ServerLoginSuccess(username, uuid));
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) < 0) {
|
||||
// Start initializing the actual join process
|
||||
initialJoinProcess();
|
||||
}
|
||||
}
|
||||
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
private void configure() {
|
||||
// Set the state to CONFIGURE to avoid false positives
|
||||
state = State.CONFIGURE;
|
||||
// Send necessary configuration packets to the client
|
||||
user.delayedWrite(synchronizeRegistry);
|
||||
user.delayedWrite(FINISH_CONFIGURATION);
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
// Set decoder state to actually catch all packets
|
||||
updateEncoderDecoderState(FallbackPacketRegistry.CONFIG);
|
||||
}
|
||||
|
||||
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
|
||||
val decoder = (FallbackPacketDecoder) user.getChannel().pipeline().get(FALLBACK_PACKET_DECODER);
|
||||
val encoder = (FallbackPacketEncoder) user.getChannel().pipeline().get(FALLBACK_PACKET_ENCODER);
|
||||
if (decoder != null && encoder != null) {
|
||||
// Update the packet registry state to be able to listen for CONFIG packets
|
||||
decoder.updateRegistry(registry);
|
||||
encoder.updateRegistry(registry);
|
||||
} else {
|
||||
// Something went wrong - the decoder should not be null
|
||||
user.getFallback().getLogger().warn("Necessary pipelines for {} not found", username);
|
||||
// Close the channel to prevent bypasses or other potential exploits
|
||||
user.getChannel().close();
|
||||
}
|
||||
}
|
||||
|
||||
private void initialJoinProcess() {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
// 1.7 players don't have KeepAlive packets in the login process
|
||||
sendJoinGamePacket();
|
||||
} else {
|
||||
// Set the state to KEEP_ALIVE to avoid false positives
|
||||
state = State.KEEP_ALIVE;
|
||||
// Generate a random KeepAlive ID for the pre-join check
|
||||
expectedKeepAliveId = RANDOM.nextInt();
|
||||
// Send first KeepAlive to check if the connection is somewhat responsive
|
||||
player.write(new KeepAlive(verifyKeepAliveId));
|
||||
user.write(new KeepAlive(expectedKeepAliveId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,18 +132,41 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Set the state to TRANSACTION to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.TRANSACTION;
|
||||
// Send a transaction with a
|
||||
player.write(new Transaction(
|
||||
0, transactionId, false
|
||||
));
|
||||
// Generate a random transaction ID
|
||||
expectedTransactionId = (short) RANDOM.nextInt();
|
||||
// Send a transaction with the given ID
|
||||
user.write(new Transaction(0, expectedTransactionId, false));
|
||||
}
|
||||
|
||||
private void sendJoinGamePacket() {
|
||||
// Set the state to CLIENT_SETTINGS to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.CLIENT_SETTINGS;
|
||||
final boolean v1_20_2 = user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0;
|
||||
if (!v1_20_2) {
|
||||
// Set the state to CLIENT_SETTINGS to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.CLIENT_SETTINGS;
|
||||
}
|
||||
// Select the JoinGame packet for the respective protocol version
|
||||
player.write(getJoinPacketForVersion(player.getProtocolVersion()));
|
||||
user.write(getJoinPacketForVersion(user.getProtocolVersion()));
|
||||
if (v1_20_2) {
|
||||
// Perform the transaction check since the ClientSettings
|
||||
// and PluginMessage packets have already been validated.
|
||||
sendTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAbilitiesAndTeleport() {
|
||||
// Set the state to TELEPORT to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.TELEPORT;
|
||||
// Make sure the player is unable to fly (the player is in spectator mode)
|
||||
user.delayedWrite(DEFAULT_ABILITIES);
|
||||
// Generate the current teleport ID
|
||||
expectedTeleportId = RANDOM.nextInt(Short.MAX_VALUE);
|
||||
// Teleport the player to the spawn position
|
||||
user.delayedWrite(new PositionLook(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
|
||||
0f, 0f, expectedTeleportId, false
|
||||
));
|
||||
}
|
||||
|
||||
private void sendChunkData() {
|
||||
@@ -117,38 +174,41 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// and go on with the flow of the verification.
|
||||
state = State.POSITION;
|
||||
// Teleport player into the fake lobby by sending an empty chunk
|
||||
player.write(EMPTY_CHUNK_DATA);
|
||||
// Send an UpdateSectionBlocks packet with a platform of blocks
|
||||
// to check if the player collides with the solid platform.
|
||||
player.write(UPDATE_SECTION_BLOCKS);
|
||||
user.delayedWrite(EMPTY_CHUNK_DATA);
|
||||
if (Sonar.get().getConfig().getVerification().isCheckCollisions()) {
|
||||
// Send an UpdateSectionBlocks packet with a platform of blocks
|
||||
// to check if the player collides with the solid platform.
|
||||
user.delayedWrite(UPDATE_SECTION_BLOCKS);
|
||||
}
|
||||
// Send all packets in one flush
|
||||
user.getChannel().flush();
|
||||
}
|
||||
|
||||
private static boolean validateClientLocale(final @NotNull FallbackPlayer<?, ?> player, final String locale) {
|
||||
private static boolean validateClientLocale(final @SuppressWarnings("unused") @NotNull FallbackUser<?, ?> user,
|
||||
final String locale) {
|
||||
// Check the client locale by performing a simple regex check on it
|
||||
final Pattern pattern = Sonar.get().getConfig().VALID_LOCALE_REGEX;
|
||||
final Pattern pattern = Sonar.get().getConfig().getVerification().getValidLocaleRegex();
|
||||
return pattern.matcher(locale).matches(); // Disallow non-ascii characters (by default)
|
||||
}
|
||||
|
||||
private static boolean validateClientBrand(final @NotNull FallbackPlayer<?, ?> player, final ByteBuf content) {
|
||||
private static boolean validateClientBrand(final @NotNull FallbackUser<?, ?> user, final ByteBuf content) {
|
||||
// We have to catch every DecoderException, so we can fail and punish
|
||||
// the player instead of only disconnecting them due to an exception.
|
||||
try {
|
||||
// No need to check for empty brands since ProtocolUtil#readBrandMessage
|
||||
// already performs these checks by default.
|
||||
final String read = ProtocolUtil.readBrandMessage(content);
|
||||
// Check if the decoded client brand string is too long
|
||||
if (read.length() > Sonar.get().getConfig().getVerification().getMaxBrandLength()) {
|
||||
return false;
|
||||
}
|
||||
// Regex pattern for validating client brands
|
||||
final Pattern pattern = Sonar.get().getConfig().VALID_BRAND_REGEX;
|
||||
// 1.7 has some very weird issues when trying to decode the client brand
|
||||
final boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0;
|
||||
final int cap = Sonar.get().getConfig().MAXIMUM_BRAND_LENGTH;
|
||||
// Read the client brand using our custom readString method that supports 1.7.
|
||||
// The legacy version of readString does not compare the string length
|
||||
// with the VarInt sent by the client.
|
||||
final String read = ProtocolUtil.readString(content, cap, legacy);
|
||||
// No need to check for empty or too long client brands since
|
||||
// ProtocolUtil#readString already does exactly that.
|
||||
final Pattern pattern = Sonar.get().getConfig().getVerification().getValidBrandRegex();
|
||||
return !read.equals("Vanilla") // The normal brand is always lowercase
|
||||
&& pattern.matcher(read).matches(); // Disallow non-ascii characters (by default)
|
||||
} catch (DecoderException exception) {
|
||||
// Fail if the string (client brand) could not be decoded properly
|
||||
player.fail("could not decode string");
|
||||
user.fail("could not decode string");
|
||||
// Throw the exception so we don't continue checking
|
||||
throw exception;
|
||||
}
|
||||
@@ -156,26 +216,52 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
// The player has already been verified, drop all other packets
|
||||
if (state == State.SUCCESS) return;
|
||||
|
||||
// Check if the player is not sending a ton of packets to the server
|
||||
final int maxPackets = Sonar.get().getConfig().MAXIMUM_LOGIN_PACKETS
|
||||
+ Sonar.get().getConfig().MAXIMUM_MOVEMENT_TICKS;
|
||||
checkFrame(++receivedPackets < maxPackets, "too many packets");
|
||||
final int maxPackets = Sonar.get().getConfig().getVerification().getMaxLoginPackets() + maxPredictionTick;
|
||||
checkFrame(++totalReceivedPackets < maxPackets, "too many packets");
|
||||
|
||||
// Check for timeout since the player could be sending packets but not important ones
|
||||
final long timeout = Sonar.get().getConfig().VERIFICATION_TIMEOUT;
|
||||
final long timeout = Sonar.get().getConfig().getVerification().getMaxPing();
|
||||
// Check if the time limit has exceeded
|
||||
if (login.delay() > timeout) {
|
||||
player.getChannel().close();
|
||||
if (login.elapsed(timeout)) {
|
||||
user.getChannel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof LoginAcknowledged) {
|
||||
// Check if we are currently expecting a LoginAcknowledged packet
|
||||
assertState(State.LOGIN_ACK);
|
||||
|
||||
// Start the configuration process for 1.20.2 clients
|
||||
configure();
|
||||
}
|
||||
|
||||
if (packet instanceof FinishConfiguration) {
|
||||
// Check if we are currently expecting a FinishConfiguration packet
|
||||
assertState(State.CONFIGURE);
|
||||
|
||||
// Check if the client has already sent valid ClientSettings and PluginMessage packets
|
||||
checkFrame(resolvedClientBrand, "did not resolve client brand");
|
||||
checkFrame(resolvedClientSettings, "did not resolve client settings");
|
||||
|
||||
// Start initializing the actual join process
|
||||
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
|
||||
initialJoinProcess();
|
||||
}
|
||||
|
||||
if (packet instanceof KeepAlive) {
|
||||
final KeepAlive keepAlive = (KeepAlive) packet;
|
||||
|
||||
// 1.7-1.8.9 are sending a KeepAlive packet with the ID 0 every 20 ticks
|
||||
if (keepAlive.getId() == 0 && user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) return;
|
||||
|
||||
// Check if we are currently expecting a KeepAlive packet
|
||||
assertState(State.KEEP_ALIVE);
|
||||
|
||||
final KeepAlive keepAlive = (KeepAlive) packet;
|
||||
|
||||
checkFrame(keepAlive.getId() == verifyKeepAliveId, "invalid KeepAlive ID");
|
||||
checkFrame(keepAlive.getId() == expectedKeepAliveId, "invalid KeepAlive ID");
|
||||
|
||||
// The correct KeepAlive packet has been received
|
||||
sendJoinGamePacket();
|
||||
@@ -185,35 +271,44 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
final ClientSettings clientSettings = (ClientSettings) packet;
|
||||
|
||||
// Validate the locale using a regex to filter unwanted characters.
|
||||
checkFrame(validateClientLocale(player, clientSettings.getLocale()), "invalid locale");
|
||||
checkFrame(validateClientLocale(user, clientSettings.getLocale()), "invalid locale");
|
||||
|
||||
// Clients sometimes mess up the ClientSettings or PluginMessage packet.
|
||||
if (state == State.CLIENT_SETTINGS) {
|
||||
state = State.PLUGIN_MESSAGE;
|
||||
}
|
||||
|
||||
// Make sure we mark the ClientSettings as valid
|
||||
resolvedClientSettings = true;
|
||||
}
|
||||
|
||||
if (packet instanceof PluginMessage) {
|
||||
final PluginMessage pluginMessage = (PluginMessage) packet;
|
||||
|
||||
// Only the brand channel is important
|
||||
// Only the brand channel is important, drop the rest
|
||||
if (pluginMessage.getChannel().equals("MC|Brand")
|
||||
|| pluginMessage.getChannel().equals("minecraft:brand")) {
|
||||
// Check if the brand packet was sent twice,
|
||||
// which is not possible when using a vanilla Minecraft client.
|
||||
checkFrame(!resolvedClientBrand, "duplicate client brand packet");
|
||||
|
||||
// Check if the channel is correct - 1.13 uses the new namespace
|
||||
// system ('minecraft:' + channel) and anything below 1.13 uses
|
||||
// the legacy namespace system ('MC|' + channel).
|
||||
final boolean v1_13 = player.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
|
||||
final boolean v1_13 = user.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
|
||||
checkFrame(pluginMessage.getChannel().equals("MC|Brand") || v1_13, "invalid channel");
|
||||
|
||||
// Validate the client branding using a regex to filter unwanted characters.
|
||||
checkFrame(validateClientBrand(player, pluginMessage.content()), "invalid client brand");
|
||||
checkFrame(validateClientBrand(user, pluginMessage.content()), "invalid client brand");
|
||||
|
||||
// Clients sometimes mess up the ClientSettings or PluginMessage packet.
|
||||
if (state == State.PLUGIN_MESSAGE) {
|
||||
// Send the transaction packet
|
||||
sendTransaction();
|
||||
}
|
||||
|
||||
// Make sure we mark the PluginMessage as valid
|
||||
resolvedClientBrand = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,27 +321,33 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// The transaction should always be accepted
|
||||
checkFrame(transaction.isAccepted(), "transaction not accepted?!");
|
||||
// Check if the transaction ID is valid
|
||||
checkFrame(transaction.getId() == transactionId, "invalid transaction id");
|
||||
checkFrame(transaction.getId() == expectedTransactionId, "invalid transaction id");
|
||||
|
||||
// Checking gravity is disabled, just finish verification
|
||||
if (!Sonar.get().getConfig().CHECK_GRAVITY) {
|
||||
if (!Sonar.get().getConfig().getVerification().isCheckGravity()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the player is unable to fly (the player is in spectator mode)
|
||||
player.write(DEFAULT_ABILITIES);
|
||||
// Teleport the player to the spawn position
|
||||
player.write(SPAWN_TELEPORT);
|
||||
// TODO: implement ChunkData packet for 1.20.2 and fix block state Ids for barriers
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// First, send an Abilities packet to the client to make
|
||||
// sure the player falls even in spectator mode.
|
||||
// Then, teleport the player to the spawn position.
|
||||
sendAbilitiesAndTeleport();
|
||||
|
||||
// 1.7-1.8 clients do not have a TeleportConfirm packet
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
|
||||
// Immediately send the chunk data as 1.7-1.8 can't confirm teleports
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
|
||||
// Immediately send the chunk data as 1.7-1.8 can't confirm teleport packets
|
||||
sendChunkData();
|
||||
} else {
|
||||
// Set the state to TELEPORT to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
state = State.TELEPORT;
|
||||
// Send all previously sent packets in one flush since we didn't flush
|
||||
// the channel earlier when we teleported the player.
|
||||
user.getChannel().flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,47 +355,53 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// Check if we are currently expecting a TeleportConfirm packet
|
||||
assertState(State.TELEPORT);
|
||||
|
||||
// Check if the teleport ID is correct
|
||||
final TeleportConfirm teleportConfirm = (TeleportConfirm) packet;
|
||||
checkFrame(teleportConfirm.getTeleportId() == expectedTeleportId, "invalid teleport ID");
|
||||
|
||||
// Check if the teleport id is correct
|
||||
final boolean teleportIdCorrect = teleportConfirm.getTeleportId() == SPAWN_TELEPORT.getTeleportId();
|
||||
checkFrame(teleportIdCorrect, "invalid teleport ID");
|
||||
// Reset all values to ensure safety on teleport
|
||||
tick = 1;
|
||||
posY = lastY = -1;
|
||||
expectedTeleportId = -1;
|
||||
|
||||
// Now we can send the chunk data
|
||||
sendChunkData();
|
||||
}
|
||||
|
||||
// Only handle positions if the player can send position packets.
|
||||
if (state.canMove) {
|
||||
if (state != State.LOGIN_ACK) {
|
||||
if (packet instanceof Position) {
|
||||
final Position position = (Position) packet;
|
||||
|
||||
// Immediately handle new position update
|
||||
handlePositionUpdate(position.getX(), position.getY(), position.getZ(), position.isOnGround());
|
||||
}
|
||||
|
||||
if (packet instanceof PositionLook) {
|
||||
final PositionLook position = (PositionLook) packet;
|
||||
|
||||
// Immediately handle new position update
|
||||
handlePositionUpdate(position.getX(), position.getY(), position.getZ(), position.isOnGround());
|
||||
}
|
||||
|
||||
if (packet instanceof Player) {
|
||||
final Player player = (Player) packet;
|
||||
|
||||
// This packet does not send new position data, reuse the last Y
|
||||
handlePositionUpdate(lastX, lastY, lastZ, player.isOnGround());
|
||||
handlePositionUpdate(posX, posY, posZ, player.isOnGround());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePositionUpdate(final double x, final double y, final double z, final boolean ground) {
|
||||
final double deltaY = lastY - y;
|
||||
// 1.8 does not have a TeleportConfirm packet, so we need to handle the spawning differently
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0
|
||||
&& expectedTeleportId != -1 // Check if the teleport ID is currently unset
|
||||
// Then, check if the position is equal to the spawn position
|
||||
&& x == SPAWN_X_POSITION && y == dynamicSpawnYPosition && z == SPAWN_Z_POSITION) {
|
||||
// Reset all values to ensure safety on teleport
|
||||
tick = 1;
|
||||
posY = -1;
|
||||
expectedTeleportId = -1;
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
lastZ = z;
|
||||
posX = x;
|
||||
lastY = posY;
|
||||
posY = y;
|
||||
posZ = z;
|
||||
|
||||
// The player is not allowed to move away from the collision platform.
|
||||
// This should not happen unless the max movement tick is configured to a high number.
|
||||
@@ -304,56 +411,85 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
// The onGround property can never be true when we aren't checking for collisions
|
||||
checkFrame(!ground || state == State.COLLISIONS, "invalid ground state");
|
||||
|
||||
// Skip teleports using this small check
|
||||
if (deltaY > 0.07) {
|
||||
// Check if the client hasn't moved before sending the first movement packet
|
||||
if (!listenForMovements) {
|
||||
if (posX == SPAWN_X_POSITION && posZ == SPAWN_Z_POSITION) {
|
||||
// Now, once we verified the X and Z position, we can safely check for gravity
|
||||
listenForMovements = true;
|
||||
}
|
||||
|
||||
// Verify the player if they sent correct movement packets
|
||||
if (movementTick++ >= MAX_MOVEMENT_TICK) {
|
||||
if (Sonar.get().getConfig().CHECK_COLLISIONS) {
|
||||
if (state != State.COLLISIONS) {
|
||||
// Set the state to COLLISIONS to avoid false positives
|
||||
// and go on with the flow of the verification.
|
||||
// Now we don't care about gravity anymore,
|
||||
// we just want the player to collide with the blocks.
|
||||
state = State.COLLISIONS;
|
||||
} else {
|
||||
// Calculate the difference between the player's Y coordinate and the expected Y coordinate
|
||||
final double offsetY = DEFAULT_Y_COLLIDE_POSITION - y;
|
||||
lastY = dynamicSpawnYPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
// The offset cannot greater than 0 since the blocks will not let the player fall through them.
|
||||
checkFrame(offsetY <= 0, "no collisions: " + offsetY);
|
||||
final double deltaY = lastY - y;
|
||||
// The deltaY is 0 whenever the player sends their first position packet.
|
||||
// We have to account for this or the player will fail the verification.
|
||||
if (deltaY == 0) {
|
||||
// Check for too many ignored Y ticks
|
||||
final int maxIgnoredTicks = Sonar.get().getConfig().getVerification().getMaxIgnoredTicks();
|
||||
checkFrame(++ignoredMovementTicks < maxIgnoredTicks, "too many ignored ticks");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is colliding by performing a basic Y offset check.
|
||||
if (ground) {
|
||||
// The player is colliding, finish verification
|
||||
// TODO: Check for: velocity, entities/mounting, interactions
|
||||
finish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Checking collisions is disabled, just finish verification
|
||||
finish();
|
||||
}
|
||||
} else if (y >= DEFAULT_Y_COLLIDE_POSITION && y <= DYNAMIC_SPAWN_Y_POSITION) {
|
||||
// This is a basic gravity check that predicts the next y position
|
||||
final double predictedY = PREPARED_MOVEMENT_PACKETS[movementTick];
|
||||
final double offsetY = Math.abs(deltaY - predictedY);
|
||||
if (tick > maxMovementTick) {
|
||||
if (!Sonar.get().getConfig().getVerification().isCheckCollisions()) {
|
||||
// Checking collisions is disabled, just finish verification
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the y motion is roughly equal to the predicted value
|
||||
checkFrame(offsetY < 0.01, "invalid offset: " + y + ", " + offsetY);
|
||||
if (state != State.COLLISIONS) {
|
||||
// Now we don't care about gravity anymore,
|
||||
// we just want the player to collide with the blocks.
|
||||
state = State.COLLISIONS;
|
||||
}
|
||||
|
||||
// Calculate the difference between the player's Y coordinate and the expected Y coordinate
|
||||
final double offsetY = DEFAULT_Y_COLLIDE_POSITION - y;
|
||||
|
||||
// Log/debug position if enabled in the configuration
|
||||
if (Sonar.get().getConfig().getVerification().isDebugXYZPositions()) {
|
||||
user.getFallback().getLogger().info("{}: {}/{}/{} - offset: {}", username, x, y, z, offsetY);
|
||||
}
|
||||
|
||||
// Check if the player is colliding by performing a basic Y offset check.
|
||||
// The offset cannot be greater than 0 since the blocks will not let the player fall through them.
|
||||
checkFrame(offsetY <= 0, "no collisions: " + offsetY);
|
||||
|
||||
if (ground) {
|
||||
// The player is colliding to blocks, finish verification
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ground && tick < maxPredictionTick) {
|
||||
final double predictedY = preparedCachedYMotions[tick];
|
||||
final double offsetY = Math.abs(deltaY - predictedY);
|
||||
|
||||
// Log/debug position if enabled in the configuration
|
||||
if (Sonar.get().getConfig().getVerification().isDebugXYZPositions()) {
|
||||
user.getFallback().getLogger().info("{}: {}/{}/{} - deltaY: {} - prediction: {} - offset: {}",
|
||||
username, x, y, z, deltaY, predictedY, offsetY);
|
||||
}
|
||||
|
||||
// Check if the y motion is roughly equal to the predicted value
|
||||
checkFrame(offsetY < 0.005, String.format("invalid gravity: %d, %.7f, %.10f, %.10f != %.10f",
|
||||
tick, y, offsetY, deltaY, predictedY));
|
||||
}
|
||||
tick++;
|
||||
}
|
||||
|
||||
private void assertState(final @NotNull State expectedState) {
|
||||
if (expectedState != state) {
|
||||
player.fail("expected " + expectedState + ", got " + state);
|
||||
user.fail("expected " + expectedState + ", got " + state);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFrame(final boolean condition, final String message) {
|
||||
if (!condition) {
|
||||
player.fail(message);
|
||||
user.fail(message);
|
||||
throw new CorruptedFrameException(message);
|
||||
}
|
||||
}
|
||||
@@ -361,21 +497,21 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
|
||||
private void finish() {
|
||||
state = State.SUCCESS;
|
||||
|
||||
player.getFallback().getConnected().remove(username);
|
||||
user.getFallback().getConnected().remove(username);
|
||||
|
||||
// Add verified player to the database
|
||||
final VerifiedPlayer verifiedPlayer = new VerifiedPlayer(
|
||||
player.getInetAddress().toString(), uuid, login.getStart()
|
||||
);
|
||||
final VerifiedPlayer verifiedPlayer = new VerifiedPlayer(user.getInetAddress().toString(), playerUuid,
|
||||
login.getStart());
|
||||
Sonar.get().getVerifiedPlayerController().add(verifiedPlayer);
|
||||
|
||||
// Disconnect player with the verification success message
|
||||
player.disconnect(Sonar.get().getConfig().VERIFICATION_SUCCESS);
|
||||
// Call the VerifySuccessEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(username, playerUuid, user, login.delay()));
|
||||
|
||||
player.getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().VERIFICATION_SUCCESSFUL_LOG
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", login.formattedDelay())
|
||||
);
|
||||
// Disconnect player with the verification success message
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
|
||||
|
||||
user.getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getSuccessLog()
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", login.toString()));
|
||||
}
|
||||
}
|
||||
+55
-34
@@ -21,79 +21,100 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_2;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.SERVERBOUND;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
private final FallbackPlayer<?, ?> connection;
|
||||
private final FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
private final FallbackUser<?, ?> user;
|
||||
private final FallbackPacketListener listener;
|
||||
private @NotNull FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
public FallbackPacketDecoder(final @NotNull FallbackPlayer<?, ?> connection,
|
||||
public FallbackPacketDecoder(final @NotNull FallbackUser<?, ?> user,
|
||||
final @NotNull FallbackPacketListener listener) {
|
||||
this.connection = connection;
|
||||
this.registry = FallbackPacketRegistry.SONAR.getProtocolRegistry(
|
||||
FallbackPacketRegistry.Direction.SERVERBOUND, connection.getProtocolVersion()
|
||||
);
|
||||
this.user = user;
|
||||
this.registry = (user.getProtocolVersion().compareTo(MINECRAFT_1_20_2) >= 0 ? LOGIN : GAME)
|
||||
.getProtocolRegistry(SERVERBOUND, user.getProtocolVersion());
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry) {
|
||||
this.registry = registry.getProtocolRegistry(SERVERBOUND, user.getProtocolVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Object msg) throws Exception {
|
||||
if (msg instanceof ByteBuf) {
|
||||
final ByteBuf byteBuf = (ByteBuf) msg;
|
||||
|
||||
// Release the ByteBuf if the connection is not active
|
||||
// or the ByteBuf doesn't contain any data to avoid
|
||||
// memory leaks or other potential exploits.
|
||||
if (!ctx.channel().isActive() || !byteBuf.isReadable()) {
|
||||
byteBuf.release();
|
||||
return;
|
||||
}
|
||||
|
||||
final int originalReaderIndex = byteBuf.readerIndex();
|
||||
// Read the packet ID and then create the packet from it
|
||||
final int packetId = readVarInt(byteBuf);
|
||||
final FallbackPacket packet = registry.createPacket(packetId);
|
||||
|
||||
// If the packet hasn't been found, skip it
|
||||
// TODO: Can we fail the verification afterwards?
|
||||
if (packet == null) {
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
} else {
|
||||
try {
|
||||
doLengthSanityChecks(byteBuf, packet);
|
||||
|
||||
try {
|
||||
packet.decode(byteBuf, connection.getProtocolVersion());
|
||||
} catch (Throwable throwable) {
|
||||
connection.fail("could not decode packet");
|
||||
throw new CorruptedFrameException("Failed to decode packet");
|
||||
}
|
||||
|
||||
if (byteBuf.isReadable()) {
|
||||
connection.fail("could not read packet to end");
|
||||
throw new CorruptedFrameException("Could not read packet to end");
|
||||
}
|
||||
|
||||
listener.handle(packet);
|
||||
} finally {
|
||||
byteBuf.release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure that the packet isn't too large or too small
|
||||
doLengthSanityChecks(byteBuf, packet);
|
||||
|
||||
try {
|
||||
// Try to decode the packet for the given protocol version
|
||||
packet.decode(byteBuf, user.getProtocolVersion());
|
||||
} catch (Throwable throwable) {
|
||||
user.fail("failed to decode packet (" + byteBuf.readableBytes() + " bytes)");
|
||||
throw new CorruptedFrameException("Failed to decode packet");
|
||||
}
|
||||
|
||||
// Check if the packet still has bytes left after we decoded it
|
||||
if (byteBuf.isReadable()) {
|
||||
user.fail("could not read packet to end (" + byteBuf.readableBytes() + " bytes left)");
|
||||
throw new CorruptedFrameException("Could not read packet to end");
|
||||
}
|
||||
|
||||
// Let our verification handler process the packet
|
||||
listener.handle(packet);
|
||||
} finally {
|
||||
// Release the ByteBuf to avoid memory leaks
|
||||
byteBuf.release();
|
||||
}
|
||||
} else {
|
||||
// Packets are always ByteBufs - if we detect an unknown object, fail verification
|
||||
user.fail("packet is not a ByteBuf");
|
||||
throw new CorruptedFrameException("Packet is not a ByteBuf?!");
|
||||
}
|
||||
}
|
||||
|
||||
private void doLengthSanityChecks(final ByteBuf byteBuf,
|
||||
final @NotNull FallbackPacket packet) throws Exception {
|
||||
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, connection.getProtocolVersion());
|
||||
final int expectedMaxLen = packet.expectedMaxLength(byteBuf, user.getProtocolVersion());
|
||||
if (expectedMaxLen != -1 && byteBuf.readableBytes() > expectedMaxLen) {
|
||||
connection.fail("packet too large");
|
||||
user.fail("packet too large (" + byteBuf.readableBytes() + " bytes)");
|
||||
throw new CorruptedFrameException("Packet too large");
|
||||
}
|
||||
|
||||
final int expectedMinLen = packet.expectedMinLength(byteBuf, connection.getProtocolVersion());
|
||||
final int expectedMinLen = packet.expectedMinLength(byteBuf, user.getProtocolVersion());
|
||||
if (byteBuf.readableBytes() < expectedMinLen) {
|
||||
connection.fail("packet too small");
|
||||
user.fail("packet too small (" + byteBuf.readableBytes() + " bytes)");
|
||||
throw new CorruptedFrameException("Packet too small");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -20,19 +20,24 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.Direction.CLIENTBOUND;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.LOGIN;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPacket> {
|
||||
private final ProtocolVersion protocolVersion;
|
||||
private final FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
|
||||
public FallbackPacketEncoder(final ProtocolVersion protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.registry = FallbackPacketRegistry.SONAR.getProtocolRegistry(
|
||||
FallbackPacketRegistry.Direction.CLIENTBOUND, protocolVersion
|
||||
);
|
||||
updateRegistry(LOGIN);
|
||||
}
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry) {
|
||||
this.registry = registry.getProtocolRegistry(CLIENTBOUND, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+89
-54
@@ -22,7 +22,11 @@ import io.netty.util.collection.IntObjectMap;
|
||||
import lombok.Data;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.*;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistrySync;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledged;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccess;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
@@ -32,16 +36,38 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
// Most of this is taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
|
||||
public enum FallbackPacketRegistry {
|
||||
SONAR {
|
||||
LOGIN {
|
||||
{
|
||||
clientbound.fallback = false;
|
||||
serverbound.fallback = false;
|
||||
serverbound.register(LoginAcknowledged.class, LoginAcknowledged::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
|
||||
// LOGIN
|
||||
clientbound.register(ServerLoginSuccess.class, ServerLoginSuccess::new,
|
||||
clientbound.register(LoginSuccess.class, LoginSuccess::new,
|
||||
map(0x02, MINECRAFT_1_7_2, false));
|
||||
}
|
||||
},
|
||||
CONFIG {
|
||||
{
|
||||
clientbound.register(Disconnect.class, Disconnect::new,
|
||||
map(0x01, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(FinishConfiguration.class, FinishConfiguration::new,
|
||||
map(0x02, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(RegistrySync.class, RegistrySync::new,
|
||||
map(0x05, MINECRAFT_1_20_2, false));
|
||||
|
||||
// GAME
|
||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x00, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x01, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(FinishConfiguration.class, FinishConfiguration::new,
|
||||
map(0x02, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x03, MINECRAFT_1_20_2, false));
|
||||
}
|
||||
},
|
||||
GAME {
|
||||
{
|
||||
clientbound.register(JoinGame.class, JoinGame::new,
|
||||
map(0x01, MINECRAFT_1_7_2, true),
|
||||
map(0x23, MINECRAFT_1_9, true),
|
||||
@@ -54,7 +80,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x23, MINECRAFT_1_19, true),
|
||||
map(0x25, MINECRAFT_1_19_1, true),
|
||||
map(0x24, MINECRAFT_1_19_3, true),
|
||||
map(0x28, MINECRAFT_1_19_4, true));
|
||||
map(0x28, MINECRAFT_1_19_4, true),
|
||||
map(0x29, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false),
|
||||
map(0x1F, MINECRAFT_1_9, false),
|
||||
@@ -67,7 +94,8 @@ public enum FallbackPacketRegistry {
|
||||
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(0x23, MINECRAFT_1_19_4, false),
|
||||
map(0x24, MINECRAFT_1_20_2, false));
|
||||
clientbound.register(Disconnect.class, Disconnect::new,
|
||||
map(0x40, MINECRAFT_1_7_2, true),
|
||||
map(0x1A, MINECRAFT_1_9, true),
|
||||
@@ -80,7 +108,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x17, MINECRAFT_1_19, true),
|
||||
map(0x19, MINECRAFT_1_19_1, true),
|
||||
map(0x17, MINECRAFT_1_19_3, true),
|
||||
map(0x1A, MINECRAFT_1_19_4, true));
|
||||
map(0x1A, MINECRAFT_1_19_4, true),
|
||||
map(0x1B, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(PositionLook.class, PositionLook::new,
|
||||
map(0x08, MINECRAFT_1_7_2, true),
|
||||
map(0x2E, MINECRAFT_1_9, true),
|
||||
@@ -94,7 +123,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x36, MINECRAFT_1_19, true),
|
||||
map(0x39, MINECRAFT_1_19_1, true),
|
||||
map(0x38, MINECRAFT_1_19_3, true),
|
||||
map(0x3C, MINECRAFT_1_19_4, true));
|
||||
map(0x3C, MINECRAFT_1_19_4, true),
|
||||
map(0x3E, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(Abilities.class, Abilities::new,
|
||||
map(0x39, MINECRAFT_1_7_2, true),
|
||||
map(0x2B, MINECRAFT_1_9, true),
|
||||
@@ -108,7 +138,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x2f, MINECRAFT_1_19, true),
|
||||
map(0x31, MINECRAFT_1_19_1, true),
|
||||
map(0x30, MINECRAFT_1_19_3, true),
|
||||
map(0x34, MINECRAFT_1_19_4, true));
|
||||
map(0x34, MINECRAFT_1_19_4, true),
|
||||
map(0x36, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(EmptyChunkData.class, EmptyChunkData::new,
|
||||
map(0x21, MINECRAFT_1_7_2, true),
|
||||
map(0x20, MINECRAFT_1_9, true),
|
||||
@@ -121,33 +152,21 @@ public enum FallbackPacketRegistry {
|
||||
map(0x1F, MINECRAFT_1_19, true),
|
||||
map(0x21, MINECRAFT_1_19_1, true),
|
||||
map(0x20, MINECRAFT_1_19_3, true),
|
||||
map(0x24, MINECRAFT_1_19_4, true));
|
||||
map(0x24, MINECRAFT_1_19_4, true),
|
||||
map(0x25, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(UpdateSectionBlocks.class, UpdateSectionBlocks::new,
|
||||
map(34, MINECRAFT_1_7_2, true),
|
||||
map(16, MINECRAFT_1_9, true),
|
||||
map(15, MINECRAFT_1_13, true),
|
||||
map(16, MINECRAFT_1_15, true),
|
||||
map(15, MINECRAFT_1_16, true),
|
||||
map(59, MINECRAFT_1_16_2, true),
|
||||
map(63, MINECRAFT_1_17, true),
|
||||
map(61, MINECRAFT_1_19, true),
|
||||
map(64, MINECRAFT_1_19_1, true),
|
||||
map(63, MINECRAFT_1_19_3, true),
|
||||
map(67, MINECRAFT_1_19_4, true));
|
||||
clientbound.register(DefaultSpawnPosition.class, DefaultSpawnPosition::new,
|
||||
map(0x05, MINECRAFT_1_7_2, true),
|
||||
map(0x43, MINECRAFT_1_9, true),
|
||||
map(0x45, MINECRAFT_1_12, true),
|
||||
map(0x46, MINECRAFT_1_12_1, true),
|
||||
map(0x49, MINECRAFT_1_13, true),
|
||||
map(0x4D, MINECRAFT_1_14, true),
|
||||
map(0x4E, MINECRAFT_1_15, true),
|
||||
map(0x42, MINECRAFT_1_16, true),
|
||||
map(0x4B, MINECRAFT_1_17, true),
|
||||
map(0x4A, MINECRAFT_1_19, true),
|
||||
map(0x4D, MINECRAFT_1_19_1, true),
|
||||
map(0x4C, MINECRAFT_1_19_3, true),
|
||||
map(0x50, MINECRAFT_1_19_4, true));
|
||||
map(0x22, MINECRAFT_1_7_2, true),
|
||||
map(0x10, MINECRAFT_1_9, true),
|
||||
map(0x0F, MINECRAFT_1_13, true),
|
||||
map(0x10, MINECRAFT_1_15, true),
|
||||
map(0x0F, MINECRAFT_1_16, true),
|
||||
map(0x3B, MINECRAFT_1_16_2, true),
|
||||
map(0x3F, MINECRAFT_1_17, true),
|
||||
map(0x3D, MINECRAFT_1_19, true),
|
||||
map(0x40, MINECRAFT_1_19_1, true),
|
||||
map(0x3F, MINECRAFT_1_19_3, true),
|
||||
map(0x43, MINECRAFT_1_19_4, true),
|
||||
map(0x45, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(Transaction.class, Transaction::new,
|
||||
map(0x32, MINECRAFT_1_7_2, true),
|
||||
map(0x11, MINECRAFT_1_9, true),
|
||||
@@ -159,7 +178,20 @@ public enum FallbackPacketRegistry {
|
||||
map(0x2D, MINECRAFT_1_19, true),
|
||||
map(0x2F, MINECRAFT_1_19_1, true),
|
||||
map(0x2E, MINECRAFT_1_19_3, true),
|
||||
map(0x32, MINECRAFT_1_19_4, true));
|
||||
map(0x32, MINECRAFT_1_19_4, true),
|
||||
map(0x33, MINECRAFT_1_20_2, true));
|
||||
clientbound.register(Chat.class, Chat::new,
|
||||
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_18_2, 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));
|
||||
|
||||
serverbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false),
|
||||
@@ -173,7 +205,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x11, MINECRAFT_1_19, false),
|
||||
map(0x12, MINECRAFT_1_19_1, false),
|
||||
map(0x11, MINECRAFT_1_19_3, false),
|
||||
map(0x12, MINECRAFT_1_19_4, false));
|
||||
map(0x12, MINECRAFT_1_19_4, false),
|
||||
map(0x14, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x15, MINECRAFT_1_7_2, false),
|
||||
map(0x04, MINECRAFT_1_9, false),
|
||||
@@ -183,7 +216,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x07, MINECRAFT_1_19, false),
|
||||
map(0x08, MINECRAFT_1_19_1, false),
|
||||
map(0x07, MINECRAFT_1_19_3, false),
|
||||
map(0x08, MINECRAFT_1_19_4, false));
|
||||
map(0x08, MINECRAFT_1_19_4, false),
|
||||
map(0x09, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x17, MINECRAFT_1_7_2, false),
|
||||
map(0x09, MINECRAFT_1_9, false),
|
||||
@@ -195,7 +229,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x0C, MINECRAFT_1_19, false),
|
||||
map(0x0D, MINECRAFT_1_19_1, false),
|
||||
map(0x0C, MINECRAFT_1_19_3, false),
|
||||
map(0x0D, MINECRAFT_1_19_4, false));
|
||||
map(0x0D, MINECRAFT_1_19_4, false),
|
||||
map(0x0F, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(PositionLook.class, PositionLook::new,
|
||||
map(0x06, MINECRAFT_1_7_2, false),
|
||||
map(0x0D, MINECRAFT_1_9, false),
|
||||
@@ -208,7 +243,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x14, MINECRAFT_1_19, false),
|
||||
map(0x15, MINECRAFT_1_19_1, false),
|
||||
map(0x14, MINECRAFT_1_19_3, false),
|
||||
map(0x15, MINECRAFT_1_19_4, false));
|
||||
map(0x15, MINECRAFT_1_19_4, false),
|
||||
map(0x17, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(TeleportConfirm.class, TeleportConfirm::new,
|
||||
map(0x00, MINECRAFT_1_9, false));
|
||||
serverbound.register(Position.class, Position::new,
|
||||
@@ -223,7 +259,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x13, MINECRAFT_1_19, false),
|
||||
map(0x14, MINECRAFT_1_19_1, false),
|
||||
map(0x13, MINECRAFT_1_19_3, false),
|
||||
map(0x14, MINECRAFT_1_19_4, false));
|
||||
map(0x14, MINECRAFT_1_19_4, false),
|
||||
map(0x16, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(Player.class, Player::new,
|
||||
map(0x03, MINECRAFT_1_7_2, false),
|
||||
map(0x0F, MINECRAFT_1_9, false),
|
||||
@@ -236,7 +273,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x16, MINECRAFT_1_19, false),
|
||||
map(0x17, MINECRAFT_1_19_1, false),
|
||||
map(0x16, MINECRAFT_1_19_3, false),
|
||||
map(0x17, MINECRAFT_1_19_4, false));
|
||||
map(0x17, MINECRAFT_1_19_4, false),
|
||||
map(0x19, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(Transaction.class, Transaction::new,
|
||||
map(0x0F, MINECRAFT_1_7_2, false),
|
||||
map(0x05, MINECRAFT_1_9, false),
|
||||
@@ -248,7 +286,8 @@ public enum FallbackPacketRegistry {
|
||||
map(0x1F, MINECRAFT_1_19, false),
|
||||
map(0x20, MINECRAFT_1_19_1, false),
|
||||
map(0x1F, MINECRAFT_1_19_3, false),
|
||||
map(0x20, MINECRAFT_1_19_4, false));
|
||||
map(0x20, MINECRAFT_1_19_4, false),
|
||||
map(0x23, MINECRAFT_1_20_2, false));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -267,7 +306,6 @@ public enum FallbackPacketRegistry {
|
||||
|
||||
public static class PacketRegistry {
|
||||
private final Map<ProtocolVersion, ProtocolRegistry> versions;
|
||||
private boolean fallback = true;
|
||||
|
||||
PacketRegistry() {
|
||||
Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class);
|
||||
@@ -283,9 +321,6 @@ public enum FallbackPacketRegistry {
|
||||
ProtocolRegistry getProtocolRegistry(final ProtocolVersion version) {
|
||||
final ProtocolRegistry registry = versions.get(version);
|
||||
if (registry == null) {
|
||||
if (fallback) {
|
||||
return getProtocolRegistry(MINIMUM_VERSION);
|
||||
}
|
||||
throw new IllegalArgumentException("Could not find data for protocol version " + version);
|
||||
}
|
||||
return registry;
|
||||
@@ -386,7 +421,7 @@ public enum FallbackPacketRegistry {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public int getPacketId(final FallbackPacket packet) {
|
||||
public int getPacketId(final @NotNull FallbackPacket packet) {
|
||||
final int id = packetClassToId.getOrDefault(packet.getClass(), Integer.MIN_VALUE);
|
||||
|
||||
if (id == Integer.MIN_VALUE) {
|
||||
@@ -414,9 +449,9 @@ public enum FallbackPacketRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
private static PacketMapping map(final int id,
|
||||
final ProtocolVersion version,
|
||||
final boolean encodeOnly) {
|
||||
private static @NotNull PacketMapping map(final int id,
|
||||
final ProtocolVersion version,
|
||||
final boolean encodeOnly) {
|
||||
return new PacketMapping(id, version, null, encodeOnly);
|
||||
}
|
||||
}
|
||||
+85
-100
@@ -24,29 +24,27 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockPosition;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.ChangedBlock;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionInfo;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.*;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.FinishConfiguration;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.config.RegistrySync;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.Abilities;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.EmptyChunkData;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.JoinGame;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.UpdateSectionBlocks;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
|
||||
@UtilityClass
|
||||
public class FallbackPreparer {
|
||||
private final Random RANDOM = new Random();
|
||||
private final DimensionInfo DIMENSION = new DimensionInfo(
|
||||
"minecraft:overworld",
|
||||
"sonar", false, false
|
||||
);
|
||||
|
||||
// Mappings
|
||||
private final CompoundBinaryTag CHAT_TYPE_119;
|
||||
@@ -61,69 +59,70 @@ public class FallbackPreparer {
|
||||
DAMAGE_TYPE_120 = getMapping("damage_type_1_20.nbt");
|
||||
}
|
||||
|
||||
// JoinGame
|
||||
public JoinGame LEGACY_JOIN_GAME;
|
||||
private JoinGame JOIN_GAME_1_16;
|
||||
private JoinGame JOIN_GAME_1_16_2;
|
||||
private JoinGame JOIN_GAME_1_18_2;
|
||||
private JoinGame JOIN_GAME_1_19_1;
|
||||
private JoinGame JOIN_GAME_1_19_4;
|
||||
private JoinGame JOIN_GAME_1_20;
|
||||
|
||||
// Abilities
|
||||
public final Abilities DEFAULT_ABILITIES = new Abilities((byte) 0, 0f, 0f);
|
||||
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new Abilities((byte) 0, 0f, 0f);
|
||||
// Chunks
|
||||
public final EmptyChunkData EMPTY_CHUNK_DATA = new EmptyChunkData(0, 0);
|
||||
public PositionLook SPAWN_TELEPORT;
|
||||
public final FallbackPacket EMPTY_CHUNK_DATA = new EmptyChunkData(0, 0);
|
||||
// Finish Configuration
|
||||
public final FallbackPacket FINISH_CONFIGURATION = new FinishConfiguration();
|
||||
// Registry Sync
|
||||
public FallbackPacket synchronizeRegistry;
|
||||
|
||||
// JoinGame
|
||||
private FallbackPacket legacyJoinGame;
|
||||
private FallbackPacket joinGame116;
|
||||
private FallbackPacket joinGame1162;
|
||||
private FallbackPacket joinGame1182;
|
||||
private FallbackPacket joinGame1191;
|
||||
private FallbackPacket joinGame1194;
|
||||
private JoinGame joinGame120;
|
||||
|
||||
// Dimension
|
||||
private final DimensionInfo DIMENSION = new DimensionInfo(
|
||||
"minecraft:overworld", "sonar", false, false);
|
||||
|
||||
// Collisions
|
||||
public final int BLOCKS_PER_ROW = 8; // 8 * 8 = 64 (max)
|
||||
private final int SPAWN_BUFFER = 10; // player spawns at 255 + 10 (10 blocks above the platform)
|
||||
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is max
|
||||
private final int SPAWN_BUFFER = 5; // player spawns at 255 + 5 (5 blocks above the platform)
|
||||
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is the maximum Y position allowed
|
||||
|
||||
public UpdateSectionBlocks UPDATE_SECTION_BLOCKS;
|
||||
private final ChangedBlock[] CHANGED_BLOCKS = new ChangedBlock[BLOCKS_PER_ROW * BLOCKS_PER_ROW];
|
||||
|
||||
public int MAX_MOVEMENT_TICK;
|
||||
public double[] PREPARED_MOVEMENT_PACKETS;
|
||||
public int DYNAMIC_SPAWN_BUFFER = SPAWN_BUFFER;
|
||||
public int DYNAMIC_SPAWN_Y_POSITION;
|
||||
public int maxMovementTick, maxPredictionTick, dynamicSpawnYPosition;
|
||||
public double[] preparedCachedYMotions;
|
||||
public double maxFallDistance;
|
||||
public final int SPAWN_X_POSITION = BLOCKS_PER_ROW; // middle of the chunk
|
||||
public final int SPAWN_Z_POSITION = BLOCKS_PER_ROW;
|
||||
|
||||
public void prepare() {
|
||||
LEGACY_JOIN_GAME = createLegacyJoinGamePacket();
|
||||
JOIN_GAME_1_16 = createJoinGamePacket(MINECRAFT_1_16);
|
||||
JOIN_GAME_1_16_2 = createJoinGamePacket(MINECRAFT_1_16_2);
|
||||
JOIN_GAME_1_18_2 = createJoinGamePacket(MINECRAFT_1_18_2);
|
||||
JOIN_GAME_1_19_1 = createJoinGamePacket(MINECRAFT_1_19_1);
|
||||
JOIN_GAME_1_19_4 = createJoinGamePacket(MINECRAFT_1_19_4);
|
||||
JOIN_GAME_1_20 = createJoinGamePacket(MINECRAFT_1_20);
|
||||
legacyJoinGame = createJoinGamePacket(MINECRAFT_1_8);
|
||||
joinGame116 = createJoinGamePacket(MINECRAFT_1_16);
|
||||
joinGame1162 = createJoinGamePacket(MINECRAFT_1_16_2);
|
||||
joinGame1182 = createJoinGamePacket(MINECRAFT_1_18_2);
|
||||
joinGame1191 = createJoinGamePacket(MINECRAFT_1_19_1);
|
||||
joinGame1194 = createJoinGamePacket(MINECRAFT_1_19_4);
|
||||
joinGame120 = createJoinGamePacket(MINECRAFT_1_20);
|
||||
synchronizeRegistry = new RegistrySync(joinGame120.getRegistry());
|
||||
|
||||
MAX_MOVEMENT_TICK = Sonar.get().getConfig().MAXIMUM_MOVEMENT_TICKS;
|
||||
PREPARED_MOVEMENT_PACKETS = new double[MAX_MOVEMENT_TICK + 1];
|
||||
maxMovementTick = Sonar.get().getConfig().getVerification().getMaxMovementTicks();
|
||||
maxPredictionTick = maxMovementTick + 10;
|
||||
preparedCachedYMotions = new double[maxPredictionTick + 1];
|
||||
|
||||
for (int i = 0; i < MAX_MOVEMENT_TICK + 1; i++) {
|
||||
PREPARED_MOVEMENT_PACKETS[i] = -((Math.pow(0.98, i) - 1) * 3.92);
|
||||
for (int i = 0; i < maxPredictionTick + 1; i++) {
|
||||
preparedCachedYMotions[i] = -((Math.pow(0.98, i) - 1) * 3.92);
|
||||
}
|
||||
|
||||
// Adjust block and collide Y position based on max movement ticks
|
||||
double maxFallDistance = 0;
|
||||
for (final double motion : PREPARED_MOVEMENT_PACKETS) {
|
||||
maxFallDistance += motion;
|
||||
// Adjust block and collide Y position based on max fall distance
|
||||
maxFallDistance = 0;
|
||||
for (int i = 0; i < maxMovementTick; i++) {
|
||||
maxFallDistance += preparedCachedYMotions[i];
|
||||
}
|
||||
|
||||
// Set the dynamic spawn buffer
|
||||
DYNAMIC_SPAWN_BUFFER = (int) (SPAWN_BUFFER + maxFallDistance);
|
||||
final int DYNAMIC_SPAWN_BUFFER = (int) (SPAWN_BUFFER + maxFallDistance);
|
||||
// Set the dynamic block and collide Y position based on the maximum fall distance
|
||||
DYNAMIC_SPAWN_Y_POSITION = DEFAULT_Y_COLLIDE_POSITION + DYNAMIC_SPAWN_BUFFER;
|
||||
|
||||
// Prepare spawn PositionLook with the dynamic Y position
|
||||
SPAWN_TELEPORT = new PositionLook(
|
||||
8, DYNAMIC_SPAWN_Y_POSITION, 8,
|
||||
0f, 0f,
|
||||
RANDOM.nextInt(Short.MAX_VALUE),
|
||||
true
|
||||
);
|
||||
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + DYNAMIC_SPAWN_BUFFER;
|
||||
|
||||
// Prepare collision platform positions
|
||||
int index = 0;
|
||||
@@ -135,7 +134,7 @@ public class FallbackPreparer {
|
||||
z + (BLOCKS_PER_ROW / 2),
|
||||
0, 0
|
||||
);
|
||||
CHANGED_BLOCKS[index++] = new ChangedBlock(position, BlockType.STONE);
|
||||
CHANGED_BLOCKS[index++] = new ChangedBlock(position, BlockType.BARRIER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,60 +144,50 @@ public class FallbackPreparer {
|
||||
|
||||
private @Nullable CompoundBinaryTag getMapping(final @NotNull String fileName) {
|
||||
try (final InputStream inputStream = Sonar.class.getResourceAsStream("/mappings/" + fileName)) {
|
||||
return BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(inputStream),
|
||||
BinaryTagIO.Compression.GZIP
|
||||
);
|
||||
return BinaryTagIO.reader().read(Objects.requireNonNull(inputStream), BinaryTagIO.Compression.GZIP);
|
||||
} catch (Throwable throwable) {
|
||||
Sonar.get().getLogger().error("Could not load mapping {}", fileName);
|
||||
throwable.printStackTrace(System.err);
|
||||
Sonar.get().getLogger().error("Could not load mappings for {}: {}", fileName, throwable);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static JoinGame getJoinPacketForVersion(final ProtocolVersion protocolVersion) {
|
||||
public static FallbackPacket getJoinPacketForVersion(final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
|
||||
return LEGACY_JOIN_GAME; // 1.7-1.15.2
|
||||
return legacyJoinGame; // 1.7.2-1.15.2
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
|
||||
return JOIN_GAME_1_16; // 1.16-1.16.1
|
||||
return joinGame116; // 1.16-1.16.1
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_18) <= 0) {
|
||||
return JOIN_GAME_1_16_2; // 1.16.2-1.18
|
||||
return joinGame1162; // 1.16.2-1.18
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19) <= 0) {
|
||||
return JOIN_GAME_1_18_2; // 1.18.1-1.19
|
||||
return joinGame1182; // 1.18.1-1.19
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
return JOIN_GAME_1_19_1; // 1.19.1-1.19.3
|
||||
return joinGame1191; // 1.19.1-1.19.3
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_4) <= 0) {
|
||||
return JOIN_GAME_1_19_4; // 1.19.4
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_4) == 0) {
|
||||
return joinGame1194; // 1.19.4
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
|
||||
return JOIN_GAME_1_20; // 1.20-1.20.1
|
||||
}
|
||||
throw new IllegalStateException("Unsupported protocol version");
|
||||
}
|
||||
|
||||
private @NotNull JoinGame createLegacyJoinGamePacket() {
|
||||
final JoinGame joinGame = new JoinGame();
|
||||
|
||||
joinGame.setLevelType("flat");
|
||||
joinGame.setGamemode(Sonar.get().getConfig().GAMEMODE_ID);
|
||||
joinGame.setDimension(0);
|
||||
joinGame.setReducedDebugInfo(true);
|
||||
return joinGame;
|
||||
return joinGame120; // 1.20-latest
|
||||
}
|
||||
|
||||
// Partially taken from
|
||||
// https://github.com/Elytrium/LimboAPI/blob/master/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java#L607
|
||||
private @NotNull JoinGame createJoinGamePacket(final ProtocolVersion protocolVersion) {
|
||||
private @NotNull JoinGame createJoinGamePacket(final @NotNull ProtocolVersion protocolVersion) {
|
||||
final JoinGame joinGame = new JoinGame();
|
||||
|
||||
joinGame.setLevelType("flat");
|
||||
joinGame.setGamemode(Sonar.get().getConfig().GAMEMODE_ID);
|
||||
joinGame.setGamemode(Sonar.get().getConfig().getVerification().getGamemodeId());
|
||||
joinGame.setDimension(0);
|
||||
joinGame.setReducedDebugInfo(true);
|
||||
|
||||
// 1.7.2-1.8.9 don't need any dimension information
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) <= 0) {
|
||||
return joinGame;
|
||||
}
|
||||
|
||||
joinGame.setDimensionInfo(DIMENSION);
|
||||
|
||||
final CompoundBinaryTag.Builder registryContainer = CompoundBinaryTag.builder();
|
||||
@@ -206,7 +195,7 @@ public class FallbackPreparer {
|
||||
.add(createDimensionData(protocolVersion))
|
||||
.build();
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
final CompoundBinaryTag.Builder dimensionRegistryEntry = CompoundBinaryTag.builder();
|
||||
|
||||
dimensionRegistryEntry.putString("type", "minecraft:dimension_type");
|
||||
@@ -245,34 +234,30 @@ public class FallbackPreparer {
|
||||
))).build()
|
||||
);
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19) == 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19) == 0) {
|
||||
registryContainer.put("minecraft:chat_type", CHAT_TYPE_119);
|
||||
} else if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
registryContainer.put("minecraft:chat_type", CHAT_TYPE_1191);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_4) == 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_4) == 0) {
|
||||
registryContainer.put("minecraft:damage_type", DAMAGE_TYPE_1194);
|
||||
} else if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_20) >= 0) {
|
||||
registryContainer.put("minecraft:damage_type", DAMAGE_TYPE_120);
|
||||
}
|
||||
} else {
|
||||
registryContainer.put("dimension", encodedDimensionRegistry);
|
||||
}
|
||||
|
||||
try {
|
||||
CompoundBinaryTag currentDimensionData = encodedDimensionRegistry.getCompound(0);
|
||||
CompoundBinaryTag currentDimensionData = encodedDimensionRegistry.getCompound(0);
|
||||
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
|
||||
currentDimensionData = currentDimensionData.getCompound("element");
|
||||
}
|
||||
|
||||
joinGame.setCurrentDimensionData(currentDimensionData);
|
||||
joinGame.setLevelNames(new String[]{"minecraft:overworld"});
|
||||
joinGame.setRegistry(registryContainer.build());
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
currentDimensionData = currentDimensionData.getCompound("element");
|
||||
}
|
||||
|
||||
joinGame.setCurrentDimensionData(currentDimensionData);
|
||||
joinGame.setLevelNames(new String[]{"minecraft:overworld"});
|
||||
joinGame.setRegistry(registryContainer.build());
|
||||
return joinGame;
|
||||
}
|
||||
|
||||
@@ -289,7 +274,7 @@ public class FallbackPreparer {
|
||||
.putBoolean("respawn_anchor_works", false)
|
||||
.putBoolean("has_raids", false)
|
||||
.putInt("logical_height", 256)
|
||||
.putString("infiniburn", version.compareTo(ProtocolVersion.MINECRAFT_1_18_2) >= 0 ? "#minecraft" +
|
||||
.putString("infiniburn", version.compareTo(MINECRAFT_1_18_2) >= 0 ? "#minecraft" +
|
||||
":infiniburn_nether" : "minecraft:infiniburn_nether")
|
||||
.putDouble("coordinate_scale", 1.0)
|
||||
.putString("effects", "minecraft:overworld")
|
||||
@@ -299,7 +284,7 @@ public class FallbackPreparer {
|
||||
.putInt("monster_spawn_light_level", 0)
|
||||
.build();
|
||||
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
|
||||
if (version.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
return CompoundBinaryTag.builder()
|
||||
.putString("name", "minecraft:overworld")
|
||||
.putInt("id", 0)
|
||||
|
||||
+57
-9
@@ -17,21 +17,69 @@
|
||||
|
||||
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 static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_13;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Getter
|
||||
@SuppressWarnings("unused")
|
||||
@RequiredArgsConstructor
|
||||
public enum BlockType {
|
||||
STONE {
|
||||
@Override
|
||||
public int getId(final ProtocolVersion protocolVersion) {
|
||||
return protocolVersion.compareTo(MINECRAFT_1_13) >= 0 ? 1 : 16;
|
||||
STONE(protocolVersion -> 1),
|
||||
BARRIER(protocolVersion -> {
|
||||
// Link: https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/1.20/blocks.json
|
||||
switch (protocolVersion) {
|
||||
case MINECRAFT_1_7_2:
|
||||
case MINECRAFT_1_7_6:
|
||||
// We have to use glass for 1.7 since 1.7 doesn't have barrier blocks
|
||||
return 20;
|
||||
default:
|
||||
// 1.8-1.12.2
|
||||
return 166;
|
||||
case MINECRAFT_1_13:
|
||||
case MINECRAFT_1_13_1:
|
||||
return 6493;
|
||||
case MINECRAFT_1_13_2:
|
||||
return 6494;
|
||||
case MINECRAFT_1_14:
|
||||
case MINECRAFT_1_14_1:
|
||||
case MINECRAFT_1_14_2:
|
||||
case MINECRAFT_1_14_3:
|
||||
case MINECRAFT_1_14_4:
|
||||
case MINECRAFT_1_15:
|
||||
case MINECRAFT_1_15_1:
|
||||
case MINECRAFT_1_15_2:
|
||||
return 7000;
|
||||
case MINECRAFT_1_16:
|
||||
case MINECRAFT_1_16_1:
|
||||
return 7536;
|
||||
case MINECRAFT_1_16_2:
|
||||
case MINECRAFT_1_16_3:
|
||||
case MINECRAFT_1_16_4:
|
||||
return 7540;
|
||||
case MINECRAFT_1_17:
|
||||
case MINECRAFT_1_17_1:
|
||||
case MINECRAFT_1_18:
|
||||
case MINECRAFT_1_18_2:
|
||||
return 7754;
|
||||
case MINECRAFT_1_19:
|
||||
case MINECRAFT_1_19_1:
|
||||
return 8245;
|
||||
case MINECRAFT_1_19_3:
|
||||
return 9889;
|
||||
case MINECRAFT_1_19_4:
|
||||
return 10221;
|
||||
case MINECRAFT_1_20:
|
||||
return 10225;
|
||||
case MINECRAFT_1_20_2:
|
||||
return 1; // TODO: https://github.com/PrismarineJS/minecraft-data/issues/779
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
public abstract int getId(final ProtocolVersion protocolVersion);
|
||||
private final Function<ProtocolVersion, Integer> function;
|
||||
|
||||
public int getId(final @NotNull ProtocolVersion protocolVersion) {
|
||||
return function.apply(protocolVersion);
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.fallback.protocol.packets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionInfo;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class JoinGame implements FallbackPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private long partialHashedSeed; // 1.15+
|
||||
private short difficulty;
|
||||
private boolean isHardcore;
|
||||
private @Nullable String levelType;
|
||||
private int viewDistance; // 1.14+
|
||||
private boolean reducedDebugInfo;
|
||||
private boolean showRespawnScreen;
|
||||
private String[] levelNames; // 1.16+
|
||||
private CompoundBinaryTag registry; // 1.16+
|
||||
private DimensionInfo dimensionInfo; // 1.16+
|
||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||
private short previousGamemode; // 1.16+
|
||||
private int simulationDistance; // 1.18+
|
||||
private int portalCooldown; // 1.20+
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0) {
|
||||
encode116Up(byteBuf, protocolVersion);
|
||||
} else {
|
||||
encodeLegacy(byteBuf, protocolVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeLegacy(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeInt(entityId);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
byteBuf.writeBoolean(isHardcore);
|
||||
byteBuf.writeByte(gamemode);
|
||||
} else {
|
||||
byteBuf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9_1) >= 0) {
|
||||
byteBuf.writeInt(dimension);
|
||||
} else {
|
||||
byteBuf.writeByte(dimension);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
|
||||
byteBuf.writeByte(difficulty);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0) {
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
}
|
||||
|
||||
byteBuf.writeByte(1); // max players
|
||||
|
||||
writeString(byteBuf, Objects.requireNonNull(levelType));
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0) {
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
}
|
||||
}
|
||||
|
||||
private void encode116Up(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeInt(entityId);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
byteBuf.writeBoolean(isHardcore);
|
||||
byteBuf.writeByte(gamemode);
|
||||
} else {
|
||||
byteBuf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
|
||||
}
|
||||
|
||||
byteBuf.writeByte(previousGamemode);
|
||||
|
||||
writeStringArray(byteBuf, levelNames);
|
||||
writeCompoundTag(byteBuf, registry);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19) < 0) {
|
||||
writeCompoundTag(byteBuf, currentDimensionData);
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
} else {
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
writeString(byteBuf, dimensionInfo.getLevelName());
|
||||
}
|
||||
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0) {
|
||||
writeVarInt(byteBuf, 1); // max players
|
||||
} else {
|
||||
byteBuf.writeByte(1); // max players
|
||||
}
|
||||
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_18) >= 0) {
|
||||
writeVarInt(byteBuf, simulationDistance);
|
||||
}
|
||||
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
|
||||
byteBuf.writeBoolean(dimensionInfo.isDebug());
|
||||
byteBuf.writeBoolean(dimensionInfo.isFlat());
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
// no last death location
|
||||
byteBuf.writeBoolean(false);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20) >= 0) {
|
||||
writeVarInt(byteBuf, portalCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+11
-19
@@ -15,34 +15,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.config;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class Position implements FallbackPacket {
|
||||
private double x, y, z;
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
x = byteBuf.readDouble();
|
||||
y = byteBuf.readDouble();
|
||||
z = byteBuf.readDouble();
|
||||
onGround = byteBuf.readBoolean();
|
||||
}
|
||||
public final class FinishConfiguration implements FallbackPacket {
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -15,42 +15,41 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.config;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_14;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_17;
|
||||
import java.io.IOException;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class DefaultSpawnPosition implements FallbackPacket {
|
||||
private int x, y, z;
|
||||
private float angle;
|
||||
public final class RegistrySync implements FallbackPacket {
|
||||
private CompoundBinaryTag dimensionRegistry;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
final long encoded = protocolVersion.compareTo(MINECRAFT_1_14) < 0
|
||||
? ((x & 0x3FFFFFFL) << 38) | ((y & 0xFFFL) << 26) | (z & 0x3FFFFFFL)
|
||||
: ((x & 0x3FFFFFFL) << 38) | ((y & 0x3FFFFFFL) << 12) | (z & 0xFFFL);
|
||||
|
||||
byteBuf.writeLong(encoded);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
|
||||
byteBuf.writeFloat(angle);
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
try (final ByteBufOutputStream stream = new ByteBufOutputStream(byteBuf)) {
|
||||
stream.writeByte(10); // CompoundTag ID
|
||||
BinaryTagTypes.COMPOUND.write(dimensionRegistry, stream);
|
||||
} catch (IOException exception) {
|
||||
throw new EncoderException("Cannot write NBT CompoundTag");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.fallback.protocol.packets.login;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
public final class LoginAcknowledged implements FallbackPacket {
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -15,12 +15,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.login;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.netty.FastUUID;
|
||||
@@ -34,12 +35,12 @@ import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class ServerLoginSuccess implements FallbackPacket {
|
||||
public final class LoginSuccess implements FallbackPacket {
|
||||
private String username;
|
||||
private UUID uuid;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
writeUUID(byteBuf, uuid);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0) {
|
||||
+4
-3
@@ -15,13 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -34,14 +35,14 @@ public class Abilities implements FallbackPacket {
|
||||
private float flySpeed, walkSpeed;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeByte(encodedFlags);
|
||||
byteBuf.writeFloat(flySpeed);
|
||||
byteBuf.writeFloat(walkSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
encodedFlags = byteBuf.readByte();
|
||||
flySpeed = byteBuf.readFloat();
|
||||
walkSpeed = byteBuf.readFloat();
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeUUID;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class Chat implements FallbackPacket {
|
||||
private static final UUID PLACEHOLDER_UUID = new UUID(0L, 0L);
|
||||
private Component component;
|
||||
private byte position;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
final String serialized = JSONComponentSerializer.json().serialize(component);
|
||||
writeString(byteBuf, serialized);
|
||||
|
||||
// Message type
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
byteBuf.writeBoolean(position == 2);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
writeVarInt(byteBuf, position);
|
||||
} else {
|
||||
byteBuf.writeByte(this.position);
|
||||
}
|
||||
|
||||
// UUID
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19) < 0) {
|
||||
writeUUID(byteBuf, PLACEHOLDER_UUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -15,13 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -55,7 +56,7 @@ public final class ClientSettings implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
locale = readString(byteBuf, 16);
|
||||
viewDistance = byteBuf.readByte();
|
||||
chatVisibility = readVarInt(byteBuf);
|
||||
+11
-9
@@ -15,20 +15,22 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.readString;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
|
||||
|
||||
@Getter
|
||||
@@ -38,17 +40,17 @@ import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeStrin
|
||||
public final class Disconnect implements FallbackPacket {
|
||||
private @Nullable String reason;
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
reason = readString(byteBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
writeString(byteBuf, Objects.requireNonNull(reason));
|
||||
}
|
||||
|
||||
public static Disconnect create(final String serialized) {
|
||||
return new Disconnect(serialized);
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static @NotNull Disconnect create(final Component component) {
|
||||
return new Disconnect(JSONComponentSerializer.json().serialize(component));
|
||||
}
|
||||
}
|
||||
+27
-38
@@ -15,28 +15,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.LongArrayBinaryTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeArray;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeCompoundTag;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
// Partially taken from
|
||||
// https://github.com/Leymooo/BungeeCord/blob/master/protocol/src/main/java/ru/leymooo/botfilter/packets/EmptyChunkPacket.java
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class EmptyChunkData implements FallbackPacket {
|
||||
@@ -45,8 +43,9 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
private static final byte[] SECTION_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
|
||||
private static final byte[] LIGHT_BYTES = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
|
||||
|
||||
private static final byte[] LEGACY_FILLER_BYTES_17 = new byte[2];
|
||||
private static final byte[] LEGACY_FILLER_BYTES = new byte[256];
|
||||
private static final byte[] MODERN_FILLER_BYTES = new byte[1024];
|
||||
private static final byte[] MODERN_FILLER_BYTES = new byte[256 * 4];
|
||||
|
||||
// Prepare nbt for 1.18 and pre-1.18
|
||||
private static final CompoundBinaryTag MODERN_TAG, LEGACY_TAG;
|
||||
@@ -57,48 +56,33 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
}
|
||||
|
||||
private static @NotNull CompoundBinaryTag prepareNBT(final boolean legacy) {
|
||||
final long[] arrayData = new long[legacy ? 36 : 37];
|
||||
final LongArrayBinaryTag longArray = LongArrayBinaryTag.longArrayBinaryTag(arrayData);
|
||||
|
||||
final CompoundBinaryTag motion = CompoundBinaryTag.builder()
|
||||
.put("MOTION_BLOCKING", longArray)
|
||||
.build();
|
||||
return CompoundBinaryTag.builder()
|
||||
.put("root", motion)
|
||||
.put("root", CompoundBinaryTag.builder()
|
||||
.put("MOTION_BLOCKING", LongArrayBinaryTag.longArrayBinaryTag(new long[legacy ? 36 : 37]))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeInt(x);
|
||||
byteBuf.writeInt(z);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
byteBuf.writeBoolean(true);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_17_1) <= 0) {
|
||||
writeVarInt(byteBuf, 0); // mask
|
||||
} else {
|
||||
byteBuf.writeBoolean(true); // full chunk
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0 && protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeBoolean(true);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeBoolean(true); // ignore old data
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) == 0) {
|
||||
byteBuf.writeShort(1);
|
||||
} else {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) > 0) {
|
||||
writeVarInt(byteBuf, 0);
|
||||
}
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
|
||||
final BitSet bitSet = new BitSet();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
bitSet.set(i, false);
|
||||
}
|
||||
|
||||
long[] mask = bitSet.toLongArray();
|
||||
writeVarInt(byteBuf, mask.length);
|
||||
|
||||
for (long l : mask) {
|
||||
byteBuf.writeLong(l);
|
||||
} else {
|
||||
byteBuf.writeShort(1); // fix void chunk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +105,12 @@ public final class EmptyChunkData implements FallbackPacket {
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13) < 0) {
|
||||
writeArray(byteBuf, LEGACY_FILLER_BYTES); // 1.8 - 1.12.2
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
writeArray(byteBuf, LEGACY_FILLER_BYTES); // 1.8 - 1.12.2
|
||||
} else {
|
||||
byteBuf.writeInt(0); // compressed size
|
||||
byteBuf.writeBytes(LEGACY_FILLER_BYTES_17); // 1.7
|
||||
}
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_15) < 0) {
|
||||
writeArray(byteBuf, MODERN_FILLER_BYTES); // 1.13 - 1.14.4
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_18) < 0) {
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.dimension.DimensionInfo;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class JoinGame implements FallbackPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private long partialHashedSeed; // 1.15+
|
||||
private short difficulty;
|
||||
private boolean isHardcore;
|
||||
private @Nullable String levelType;
|
||||
private int viewDistance; // 1.14+
|
||||
private boolean reducedDebugInfo;
|
||||
private boolean showRespawnScreen;
|
||||
private boolean limitedCrafting; // 1.20.2+
|
||||
private String[] levelNames; // 1.16+
|
||||
private CompoundBinaryTag registry; // 1.16+
|
||||
private DimensionInfo dimensionInfo; // 1.16+
|
||||
private CompoundBinaryTag currentDimensionData; // 1.16.2+
|
||||
private short previousGamemode; // 1.16+
|
||||
private int simulationDistance; // 1.18+
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/Nan1t/NanoLimbo/blob/main/src/main/java/ua/nanit/limbo/protocol/packets/play/PacketJoinGame.java
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeInt(entityId);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) <= 0) {
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeByte(dimension);
|
||||
byteBuf.writeByte(0); // difficulty
|
||||
byteBuf.writeByte(1); // max players
|
||||
writeString(byteBuf, "flat"); // level type
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9_1) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_13_2) <= 0) {
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeInt(dimension);
|
||||
byteBuf.writeByte(0); // difficulty
|
||||
byteBuf.writeByte(1); // max players
|
||||
writeString(byteBuf, "flat"); // level type
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_14_4) <= 0) {
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeInt(dimension);
|
||||
byteBuf.writeByte(1); // max players
|
||||
writeString(byteBuf, "flat"); // level type
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_15) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_15_2) <= 0) {
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeInt(dimension);
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
byteBuf.writeByte(1); // max players
|
||||
writeString(byteBuf, "flat"); // level type
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_16_1) <= 0) {
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeByte(previousGamemode);
|
||||
writeStringArray(byteBuf, levelNames);
|
||||
writeCompoundTag(byteBuf, registry);
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
writeString(byteBuf, dimensionInfo.getLevelName());
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
byteBuf.writeByte(1); // max players
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
byteBuf.writeBoolean(dimensionInfo.isDebug());
|
||||
byteBuf.writeBoolean(dimensionInfo.isFlat());
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_18_2) <= 0) {
|
||||
byteBuf.writeBoolean(isHardcore);
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeByte(previousGamemode);
|
||||
writeStringArray(byteBuf, levelNames);
|
||||
writeCompoundTag(byteBuf, registry);
|
||||
writeCompoundTag(byteBuf, currentDimensionData);
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
writeVarInt(byteBuf, 1); // max players
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_18) >= 0) {
|
||||
writeVarInt(byteBuf, viewDistance); // simulation distance
|
||||
}
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
byteBuf.writeBoolean(dimensionInfo.isDebug());
|
||||
byteBuf.writeBoolean(dimensionInfo.isFlat());
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_20) <= 0) {
|
||||
byteBuf.writeBoolean(isHardcore);
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeByte(previousGamemode);
|
||||
writeStringArray(byteBuf, levelNames);
|
||||
writeCompoundTag(byteBuf, registry);
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
writeString(byteBuf, dimensionInfo.getLevelName());
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
writeVarInt(byteBuf, 1); // max players
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
writeVarInt(byteBuf, viewDistance); // simulation distance
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
byteBuf.writeBoolean(dimensionInfo.isDebug());
|
||||
byteBuf.writeBoolean(dimensionInfo.isFlat());
|
||||
byteBuf.writeBoolean(false); // last death location
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20) == 0) {
|
||||
writeVarInt(byteBuf, 0); // portal cooldown
|
||||
}
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0) {
|
||||
byteBuf.writeBoolean(isHardcore);
|
||||
writeStringArray(byteBuf, levelNames);
|
||||
writeVarInt(byteBuf, 1); // max players
|
||||
writeVarInt(byteBuf, viewDistance);
|
||||
writeVarInt(byteBuf, viewDistance); // simulation distance
|
||||
byteBuf.writeBoolean(reducedDebugInfo);
|
||||
byteBuf.writeBoolean(showRespawnScreen);
|
||||
byteBuf.writeBoolean(limitedCrafting);
|
||||
writeString(byteBuf, dimensionInfo.getIdentifier());
|
||||
writeString(byteBuf, dimensionInfo.getLevelName());
|
||||
byteBuf.writeLong(partialHashedSeed);
|
||||
byteBuf.writeByte(gamemode);
|
||||
byteBuf.writeByte(previousGamemode);
|
||||
byteBuf.writeBoolean(dimensionInfo.isDebug());
|
||||
byteBuf.writeBoolean(dimensionInfo.isFlat());
|
||||
byteBuf.writeBoolean(false); // last death location
|
||||
writeVarInt(byteBuf, 0); // portal cooldown
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -15,12 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -30,13 +32,14 @@ import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class KeepAlive implements FallbackPacket {
|
||||
private long id;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
|
||||
byteBuf.writeLong(id);
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
@@ -47,7 +50,7 @@ public final class KeepAlive implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_12_2) >= 0) {
|
||||
id = byteBuf.readLong();
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
+3
-2
@@ -15,13 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -33,7 +34,7 @@ public final class Player implements FallbackPacket {
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
onGround = byteBuf.readBoolean();
|
||||
}
|
||||
|
||||
+8
-2
@@ -15,11 +15,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.netty.DeferredByteBufHolder;
|
||||
@@ -45,7 +46,12 @@ public final class PluginMessage extends DeferredByteBufHolder implements Fallba
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 0xfff + 1; // limit packet size to 4096
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
channel = readString(byteBuf);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class Position implements FallbackPacket {
|
||||
private double x, y, z;
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
x = byteBuf.readDouble();
|
||||
// https://github.com/jonesdevelopment/sonar/issues/20
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.skipBytes(8);
|
||||
}
|
||||
y = byteBuf.readDouble();
|
||||
z = byteBuf.readDouble();
|
||||
onGround = byteBuf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 33 : 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
+30
-9
@@ -15,13 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -39,8 +40,12 @@ public final class PositionLook implements FallbackPacket {
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
x = byteBuf.readDouble();
|
||||
// https://github.com/jonesdevelopment/sonar/issues/20
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
|
||||
byteBuf.skipBytes(8);
|
||||
}
|
||||
y = byteBuf.readDouble();
|
||||
z = byteBuf.readDouble();
|
||||
yaw = byteBuf.readFloat();
|
||||
@@ -49,20 +54,36 @@ public final class PositionLook implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
byteBuf.writeDouble(x);
|
||||
byteBuf.writeDouble(y);
|
||||
byteBuf.writeDouble(z);
|
||||
byteBuf.writeFloat(yaw);
|
||||
byteBuf.writeFloat(pitch);
|
||||
byteBuf.writeByte(0x00);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
}
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeBoolean(onGround);
|
||||
} else {
|
||||
byteBuf.writeByte(0x00);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0 && protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
byteBuf.writeBoolean(true); // Dismount vehicle
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_9) >= 0) {
|
||||
writeVarInt(byteBuf, teleportId);
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19_3) <= 0) {
|
||||
byteBuf.writeBoolean(true); // Always dismount vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 41 : 33;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
return 33;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
+5
-4
@@ -15,13 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@@ -36,12 +37,12 @@ public final class Transaction implements FallbackPacket {
|
||||
private boolean accepted;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
byteBuf.writeByte(windowId);
|
||||
byteBuf.writeShort((short) id);
|
||||
// The "accepted" field is actually really unnecessary since
|
||||
// the client must accept the transaction either way.
|
||||
// it's never even used in the client.
|
||||
byteBuf.writeBoolean(accepted);
|
||||
} else {
|
||||
byteBuf.writeInt(id);
|
||||
@@ -49,7 +50,7 @@ public final class Transaction implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
windowId = byteBuf.readByte();
|
||||
id = byteBuf.readShort();
|
||||
+27
-16
@@ -15,51 +15,61 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets;
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.block.ChangedBlock;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_16_2;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
|
||||
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarLong;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString(exclude = {"changedBlocks"})
|
||||
public final class UpdateSectionBlocks implements FallbackPacket {
|
||||
private int sectionX, sectionZ;
|
||||
private ChangedBlock[] changedBlocks;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
|
||||
byteBuf.writeInt(sectionX);
|
||||
byteBuf.writeInt(sectionZ);
|
||||
writeVarInt(byteBuf, changedBlocks.length);
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeShort(changedBlocks.length);
|
||||
byteBuf.writeInt(4 * changedBlocks.length);
|
||||
} else {
|
||||
writeVarInt(byteBuf, changedBlocks.length);
|
||||
}
|
||||
|
||||
for (final ChangedBlock block : changedBlocks) {
|
||||
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
|
||||
writeVarInt(byteBuf, block.getType().getId(protocolVersion));
|
||||
final int id = block.getType().getId(protocolVersion);
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_13) >= 0) {
|
||||
writeVarInt(byteBuf, id);
|
||||
} else {
|
||||
final int shiftedBlockId = id << 4;
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
byteBuf.writeShort(shiftedBlockId);
|
||||
} else {
|
||||
writeVarInt(byteBuf, shiftedBlockId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final ChangedBlock lastChangedBlock = changedBlocks[changedBlocks.length - 1];
|
||||
final int sectionY = lastChangedBlock.getPosition().getY() >> 4;
|
||||
final int sectionY = changedBlocks[0].getPosition().getY() >> 4;
|
||||
|
||||
// Why is Mojang doing this? :(
|
||||
byteBuf.writeLong(
|
||||
((long) (sectionX & 0x3FFFFF) << 42)
|
||||
| (sectionY & 0xFFFFF)
|
||||
| ((long) (sectionZ & 0x3FFFFF) << 20)
|
||||
);
|
||||
byteBuf.writeLong(((sectionX & 0x3FFFFFL) << 42) | (sectionY & 0xFFFFF) | ((sectionZ & 0x3FFFFFL) << 20));
|
||||
|
||||
// 1.20+ don't have light update suppression
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20) < 0) {
|
||||
@@ -70,7 +80,8 @@ public final class UpdateSectionBlocks implements FallbackPacket {
|
||||
|
||||
for (final ChangedBlock block : changedBlocks) {
|
||||
final int shiftedBlockId = block.getType().getId(protocolVersion) << 12;
|
||||
writeVarLong(byteBuf, shiftedBlockId | block.getModernChunkPosCrammed());
|
||||
final long positionIdValue = shiftedBlockId | block.getModernChunkPosCrammed();
|
||||
writeVarLong(byteBuf, positionIdValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-8
@@ -27,13 +27,11 @@ public class TrafficChannelHooker implements FallbackPipelines {
|
||||
public void hook(final @NotNull ChannelPipeline pipeline,
|
||||
final @NotNull String decoder,
|
||||
final @NotNull String encoder) {
|
||||
pipeline.channel().eventLoop().execute(() -> {
|
||||
if (pipeline.get(FALLBACK_IN_TRAFFIC) == null && pipeline.get(decoder) != null) {
|
||||
pipeline.addBefore(decoder, FALLBACK_IN_TRAFFIC, IncomingBandwidthDecoder.INSTANCE);
|
||||
}
|
||||
if (pipeline.get(FALLBACK_OUT_TRAFFIC) == null && pipeline.get(encoder) != null) {
|
||||
pipeline.addBefore(encoder, FALLBACK_OUT_TRAFFIC, OutgoingBandwidthEncoder.INSTANCE);
|
||||
}
|
||||
});
|
||||
if (pipeline.get(FALLBACK_IN_TRAFFIC) == null && pipeline.get(decoder) != null) {
|
||||
pipeline.addBefore(decoder, FALLBACK_IN_TRAFFIC, IncomingBandwidthDecoder.INSTANCE);
|
||||
}
|
||||
if (pipeline.get(FALLBACK_OUT_TRAFFIC) == null && pipeline.get(encoder) != null) {
|
||||
pipeline.addBefore(encoder, FALLBACK_OUT_TRAFFIC, OutgoingBandwidthEncoder.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-51
@@ -25,7 +25,6 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "blacklist",
|
||||
@@ -44,81 +43,56 @@ public final class BlacklistCommand extends Subcommand {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist add <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = checkIP(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawInetAddress);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (SONAR.getFallback().getBlacklisted().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_DUPLICATE);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistDuplicate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_ADD_WARNING
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAddWarning()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
SONAR.getFallback().getBlacklisted().put(inetAddress.toString());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_ADD
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = checkIP(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
if (!IP_PATTERN.matcher(rawInetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawInetAddress);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().ILLEGAL_IP_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the IP is blacklisted
|
||||
if (!SONAR.getFallback().getBlacklisted().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_NOT_FOUND);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
SONAR.getFallback().getBlacklisted().invalidate(inetAddress.toString());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_REMOVE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
@@ -127,21 +101,19 @@ public final class BlacklistCommand extends Subcommand {
|
||||
final int blacklisted = SONAR.getFallback().getBlacklisted().estimatedSize();
|
||||
|
||||
if (blacklisted == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_EMPTY);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
SONAR.getFallback().getBlacklisted().invalidateAll();
|
||||
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().BLACKLIST_CLEARED
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted))
|
||||
);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().BLACKLIST_SIZE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklisted().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
+79
-16
@@ -17,38 +17,101 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.val;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.profiler.JVMProfiler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
// This command is only used to display helpful information in order to help
|
||||
// the Sonar developer(s) to fix issues more quickly
|
||||
// the Sonar contributors to fix issues more quickly
|
||||
@SubcommandInfo(
|
||||
name = "dump",
|
||||
description = "Print developer information",
|
||||
onlyConsole = true
|
||||
)
|
||||
public final class DumpCommand extends Subcommand implements JVMProfiler {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.disableInnerClassSerialization()
|
||||
.create();
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
final String osName = System.getProperty("os.name");
|
||||
final String osArch = System.getProperty("os.arch");
|
||||
final String jvmArgs = String.join(",", RUNTIME_MX_BEAN.getInputArguments());
|
||||
final String json = GSON.toJson(collectMappedInformation());
|
||||
SONAR.getLogger().info("Generated dump: {}", json);
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage("Virtual cores: " + getVirtualCores());
|
||||
invocation.getSender().sendMessage("JVM args: " + jvmArgs);
|
||||
invocation.getSender().sendMessage("Java name: " + RUNTIME_MX_BEAN.getVmName());
|
||||
invocation.getSender().sendMessage("Java version: " + RUNTIME_MX_BEAN.getVmVersion());
|
||||
invocation.getSender().sendMessage("Java vendor: " + RUNTIME_MX_BEAN.getVmVendor());
|
||||
invocation.getSender().sendMessage("Operating system: " + osName + ", " + osArch);
|
||||
invocation.getSender().sendMessage("Total memory: " + formatMemory(getTotalMemory()));
|
||||
invocation.getSender().sendMessage("Max memory: " + formatMemory(getMaxMemory()));
|
||||
invocation.getSender().sendMessage("Free memory: " + formatMemory(getFreeMemory()));
|
||||
invocation.getSender().sendMessage("Used Memory: " + formatMemory(getUsedMemory()));
|
||||
invocation.getSender().sendMessage("Platform: " + SONAR.getServer().getPlatform().getDisplayName());
|
||||
invocation.getSender().sendMessage("Version: " + SONAR.getVersion().getFull());
|
||||
@Unmodifiable
|
||||
private @NotNull Map<String, Object> collectMappedInformation() {
|
||||
val mappings = new WeakHashMap<String, Object>();
|
||||
mappings.put("sonar", new Dump.Sonar(
|
||||
SONAR.getVersion().getFull(),
|
||||
SONAR.getServer().getPlatform(),
|
||||
SONAR.getVersion().isOnMainBranch()
|
||||
));
|
||||
mappings.put("runtime", new Dump.Runtime(
|
||||
getVirtualCores(),
|
||||
RUNTIME_MX_BEAN.getInputArguments(),
|
||||
RUNTIME_MX_BEAN.getVmName(),
|
||||
RUNTIME_MX_BEAN.getVmVendor(),
|
||||
RUNTIME_MX_BEAN.getVmVersion()
|
||||
));
|
||||
mappings.put("os", new Dump.OS(
|
||||
System.getProperty("os.name"),
|
||||
System.getProperty("os.arch"),
|
||||
System.getProperty("os.version")
|
||||
));
|
||||
mappings.put("memory", new Dump.Memory(
|
||||
formatMemory(getTotalMemory()),
|
||||
formatMemory(getMaxMemory()),
|
||||
formatMemory(getFreeMemory()),
|
||||
formatMemory(getUsedMemory())
|
||||
));
|
||||
return mappings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final class Dump {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static final class Sonar {
|
||||
private final String version;
|
||||
private final SonarPlatform platform;
|
||||
private final boolean isOnMainBranch;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static final class Runtime {
|
||||
private final int virtualCores;
|
||||
private final List<String> jvmArguments;
|
||||
private final String vmName;
|
||||
private final String vmVendor;
|
||||
private final String vmVersion;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static final class Memory {
|
||||
private final String total;
|
||||
private final String max;
|
||||
private final String free;
|
||||
private final String used;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static final class OS {
|
||||
private final String name;
|
||||
private final String arch;
|
||||
private final String version;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-8
@@ -24,20 +24,18 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "lockdown",
|
||||
description = "Lock the server down"
|
||||
description = "Prevent all players from joining"
|
||||
)
|
||||
public final class LockdownCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
SONAR.getConfig().LOCKDOWN_ENABLED = !SONAR.getConfig().LOCKDOWN_ENABLED;
|
||||
SONAR.getConfig().getGeneralConfig().set("general.lockdown.enabled", SONAR.getConfig().LOCKDOWN_ENABLED);
|
||||
final boolean newState = !SONAR.getConfig().getLockdown().isEnabled();
|
||||
SONAR.getConfig().getLockdown().setEnabled(newState);
|
||||
SONAR.getConfig().getGeneralConfig().set("general.lockdown.enabled", newState);
|
||||
SONAR.getConfig().getGeneralConfig().save();
|
||||
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().LOCKDOWN_ENABLED
|
||||
? SONAR.getConfig().LOCKDOWN_ACTIVATED
|
||||
: SONAR.getConfig().LOCKDOWN_DEACTIVATED
|
||||
);
|
||||
invocation.getSender().sendMessage(newState
|
||||
? SONAR.getConfig().getLockdown().getActivated() : SONAR.getConfig().getLockdown().getDeactivated());
|
||||
}
|
||||
}
|
||||
+6
-8
@@ -21,24 +21,22 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "reload",
|
||||
description = "Reload the configuration"
|
||||
description = "Reload all configurations"
|
||||
)
|
||||
public final class ReloadCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final SystemTimer timer = new SystemTimer();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().RELOADING);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getReloading());
|
||||
SONAR.reload();
|
||||
|
||||
final long timeTaken = System.currentTimeMillis() - startTime;
|
||||
invocation.getSender().sendMessage(
|
||||
SONAR.getConfig().RELOADED
|
||||
.replace("%taken%", String.valueOf(timeTaken))
|
||||
);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getReloaded()
|
||||
.replace("%taken%", String.valueOf(timer.delay())));
|
||||
}
|
||||
}
|
||||
+59
-48
@@ -40,60 +40,71 @@ import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
argumentsRequired = false
|
||||
)
|
||||
public final class StatisticsCommand extends Subcommand implements JVMProfiler {
|
||||
private enum StatisticType {
|
||||
GENERAL,
|
||||
NETWORK,
|
||||
MEMORY,
|
||||
CPU
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
final String showing = invocation.getRawArguments().length >= 2
|
||||
? invocation.getRawArguments()[1].toLowerCase() : "bot";
|
||||
|
||||
invocation.getSender().sendMessage("§eShowing " + showing + " statistics for this session:");
|
||||
invocation.getSender().sendMessage("§ePlease note that session statistics are not saved when the server restarts.");
|
||||
invocation.getSender().sendMessage();
|
||||
|
||||
if (!showing.equals("bot")) {
|
||||
switch (showing) {
|
||||
case "network": {
|
||||
invocation.getSender().sendMessage(" §7▪ §aCurrent incoming used bandwidth: §f" + TrafficCounter.INCOMING.getCachedSecond());
|
||||
invocation.getSender().sendMessage(" §7▪ §aCurrent outgoing used bandwidth: §f" + TrafficCounter.OUTGOING.getCachedSecond());
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal incoming used bandwidth: §f" + TrafficCounter.INCOMING.getCachedTtl());
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal outgoing used bandwidth: §f" + TrafficCounter.OUTGOING.getCachedTtl());
|
||||
return;
|
||||
}
|
||||
|
||||
case "memory": {
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal free memory (JVM): §f" + formatMemory(getFreeMemory()));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal used memory (JVM): §f" + formatMemory(getUsedMemory()));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal maximum memory (JVM): §f" + formatMemory(getMaxMemory()));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal allocated memory (JVM): §f" + formatMemory(getTotalMemory()));
|
||||
return;
|
||||
}
|
||||
|
||||
case "cpu": {
|
||||
invocation.getSender().sendMessage(" §7▪ §aProcess CPU usage right now: §f" + DECIMAL_FORMAT.format(getProcessCPUUsage()) + "%");
|
||||
invocation.getSender().sendMessage(" §7▪ §aSystem CPU usage right now: §f" + DECIMAL_FORMAT.format(getSystemCPUUsage()) + "%");
|
||||
invocation.getSender().sendMessage(" §7▪ §aPer-core process CPU usage: §f" + DECIMAL_FORMAT.format(getAverageProcessCPUUsage()) + "%");
|
||||
invocation.getSender().sendMessage(" §7▪ §aPer-core system CPU usage: §f" + DECIMAL_FORMAT.format(getAverageSystemCPUUsage()) + "%");
|
||||
invocation.getSender().sendMessage(" §7▪ §aGeneral system load average: §f" + DECIMAL_FORMAT.format(getSystemLoadAverage()) + "%");
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal virtual cpu cores (JVM): §f" + DECIMAL_FORMAT.format(getVirtualCores()));
|
||||
return;
|
||||
}
|
||||
StatisticType type = StatisticType.GENERAL;
|
||||
if (invocation.getRawArguments().length >= 2) {
|
||||
try {
|
||||
type = StatisticType.valueOf(invocation.getRawArguments()[1].toUpperCase());
|
||||
} catch (Exception exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getUnknownStatisticType()
|
||||
.replace("%statistics%", getArguments()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final int total = Statistics.TOTAL_TRAFFIC.get();
|
||||
final int real = Statistics.REAL_TRAFFIC.get();
|
||||
final int failed = Statistics.FAILED_VERIFICATIONS.get();
|
||||
final int queued = SONAR.getFallback().getQueue().getQueuedPlayers().size();
|
||||
final int verifying = SONAR.getFallback().getConnected().size();
|
||||
final int verified = SONAR.getVerifiedPlayerController().estimatedSize();
|
||||
final int blacklisted = SONAR.getFallback().getBlacklisted().estimatedSize();
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getStatisticsHeader()
|
||||
.replace("%type%", type.name().toLowerCase()));
|
||||
invocation.getSender().sendMessage();
|
||||
|
||||
invocation.getSender().sendMessage(" §7▪ §aVerified IP addresses: §f" + DECIMAL_FORMAT.format(verified));
|
||||
invocation.getSender().sendMessage(" §7▪ §aVerifying IP addresses: §f" + DECIMAL_FORMAT.format(verifying));
|
||||
invocation.getSender().sendMessage(" §7▪ §aBlacklisted IP addresses: §f" + DECIMAL_FORMAT.format(blacklisted));
|
||||
invocation.getSender().sendMessage(" §7▪ §aCurrently queued logins: §f" + DECIMAL_FORMAT.format(queued));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal non-unique joins: §f" + DECIMAL_FORMAT.format(total));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal verification attempts: §f" + DECIMAL_FORMAT.format(real));
|
||||
invocation.getSender().sendMessage(" §7▪ §aTotal failed verifications: §f" + DECIMAL_FORMAT.format(failed));
|
||||
switch (type) {
|
||||
case GENERAL: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getGeneralStatistics()
|
||||
.replace("%verified%", DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(SONAR.getFallback().getConnected().size()))
|
||||
.replace("%blacklisted%", DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklisted().estimatedSize()))
|
||||
.replace("%queued%", DECIMAL_FORMAT.format(SONAR.getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%total_joins%", DECIMAL_FORMAT.format(Statistics.TOTAL_TRAFFIC.get()))
|
||||
.replace("%total_attempts%", DECIMAL_FORMAT.format(Statistics.REAL_TRAFFIC.get()))
|
||||
.replace("%total_failed%", DECIMAL_FORMAT.format(Statistics.FAILED_VERIFICATIONS.get())));
|
||||
break;
|
||||
}
|
||||
|
||||
case CPU: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getCpuStatistics()
|
||||
.replace("%process_cpu%", DECIMAL_FORMAT.format(getProcessCPUUsage()))
|
||||
.replace("%system_cpu%", DECIMAL_FORMAT.format(getSystemCPUUsage()))
|
||||
.replace("%average_process_cpu%", DECIMAL_FORMAT.format(getAverageProcessCPUUsage()))
|
||||
.replace("%average_system_cpu%", DECIMAL_FORMAT.format(getAverageSystemCPUUsage()))
|
||||
.replace("%load_average%", DECIMAL_FORMAT.format(getSystemLoadAverage()))
|
||||
.replace("%virtual_cores%", DECIMAL_FORMAT.format(getVirtualCores())));
|
||||
break;
|
||||
}
|
||||
|
||||
case MEMORY: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getMemoryStatistics()
|
||||
.replace("%free_memory%", formatMemory(getFreeMemory()))
|
||||
.replace("%used_memory%", formatMemory(getUsedMemory()))
|
||||
.replace("%max_memory%", formatMemory(getMaxMemory()))
|
||||
.replace("%total_memory%", formatMemory(getTotalMemory())));
|
||||
break;
|
||||
}
|
||||
|
||||
case NETWORK: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getNetworkStatistics()
|
||||
.replace("%incoming%", TrafficCounter.INCOMING.getCachedSecond())
|
||||
.replace("%outgoing%", TrafficCounter.OUTGOING.getCachedSecond())
|
||||
.replace("%ttl_incoming%", TrafficCounter.INCOMING.getCachedTtl())
|
||||
.replace("%ttl_outgoing%", TrafficCounter.OUTGOING.getCachedTtl()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-13
@@ -19,7 +19,7 @@ package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
@@ -34,33 +34,29 @@ public final class VerboseCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
InvocationSender verboseSubscriber = invocation.getSender();
|
||||
InvocationSource verboseSubscriber = invocation.getSender();
|
||||
|
||||
// Support for '/sonar verbose [username]'
|
||||
if (invocation.getRawArguments().length >= 2) {
|
||||
final Optional<InvocationSender> optional = SONAR.getServer().getOnlinePlayer(invocation.getRawArguments()[1]);
|
||||
final Optional<InvocationSource> optional = SONAR.getServer().getOnlinePlayer(invocation.getRawArguments()[1]);
|
||||
if (optional.isPresent()) verboseSubscriber = optional.get();
|
||||
}
|
||||
|
||||
if (SONAR.getVerboseHandler().isSubscribed(verboseSubscriber.getName())) {
|
||||
SONAR.getVerboseHandler().unsubscribe(verboseSubscriber.getName());
|
||||
if (verboseSubscriber != invocation.getSender()) {
|
||||
verboseSubscriber.sendMessage(
|
||||
SONAR.getConfig().VERBOSE_UNSUBSCRIBED_OTHER
|
||||
.replace("%player%", verboseSubscriber.getName())
|
||||
);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribedOther()
|
||||
.replace("%player%", verboseSubscriber.getName()));
|
||||
}
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().VERBOSE_UNSUBSCRIBED);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribed());
|
||||
return;
|
||||
}
|
||||
|
||||
if (verboseSubscriber != invocation.getSender()) {
|
||||
verboseSubscriber.sendMessage(
|
||||
SONAR.getConfig().VERBOSE_SUBSCRIBED_OTHER
|
||||
.replace("%player%", verboseSubscriber.getName())
|
||||
);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribedOther()
|
||||
.replace("%player%", verboseSubscriber.getName()));
|
||||
}
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().VERBOSE_SUBSCRIBED);
|
||||
verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribed());
|
||||
SONAR.getVerboseHandler().subscribe(verboseSubscriber.getName());
|
||||
}
|
||||
}
|
||||
+35
-39
@@ -24,6 +24,7 @@ import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -39,75 +40,70 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
}
|
||||
)
|
||||
public final class VerifiedCommand extends Subcommand {
|
||||
private static final Queue<String> LOCK = new LinkedBlockingQueue<>(10);
|
||||
private static final Queue<String> LOCK = new LinkedBlockingQueue<>(1);
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull CommandInvocation invocation) {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "history": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified history <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String inetAddress = invocation.getRawArguments()[2];
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = checkIP(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
if (!IP_PATTERN.matcher(inetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
// Make sure the IP is verified already
|
||||
if (!SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to parse this, so we can just use the string.
|
||||
final String realInetAddress = "/" + inetAddress;
|
||||
|
||||
if (!SONAR.getVerifiedPlayerController().has(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage("§ePrevious UUIDs for " + inetAddress + ":");
|
||||
|
||||
for (final UUID uuid : SONAR.getVerifiedPlayerController().getUUIDs(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(" §7▪ §f" + uuid.toString());
|
||||
invocation.getSender().sendMessage("<yellow>Previous UUIDs for " + rawInetAddress + ":");
|
||||
for (final UUID uuid : SONAR.getVerifiedPlayerController().getUUIDs(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(" <gray>▪ <white>" + uuid.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_COMMAND_USAGE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String inetAddress = invocation.getRawArguments()[2];
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
if (!IP_PATTERN.matcher(inetAddress).matches()) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().INCORRECT_IP_ADDRESS);
|
||||
// Make sure we aren't currently locking the IP address to avoid a double I/O operation
|
||||
if (LOCK.contains(rawInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedBlocked());
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to parse this, so we can just use the string.
|
||||
final String realInetAddress = "/" + inetAddress;
|
||||
final InetAddress inetAddress = checkIP(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
if (LOCK.contains(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_BLOCKED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SONAR.getVerifiedPlayerController().has(realInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_NOT_FOUND);
|
||||
// Make sure the player is verified already
|
||||
if (!SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock the IP address
|
||||
// Make sure we don't accidentally run 2 operations at the same time
|
||||
LOCK.add(realInetAddress);
|
||||
SONAR.getVerifiedPlayerController().remove(realInetAddress);
|
||||
LOCK.remove(realInetAddress);
|
||||
LOCK.add(rawInetAddress);
|
||||
SONAR.getVerifiedPlayerController().remove(inetAddress.toString());
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_REMOVE
|
||||
.replace("%ip%", inetAddress));
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
// Unlock the IP address
|
||||
LOCK.remove(rawInetAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -115,19 +111,19 @@ public final class VerifiedCommand extends Subcommand {
|
||||
final int verified = SONAR.getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
if (verified == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_EMPTY);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
SONAR.getVerifiedPlayerController().clearAll();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_CLEARED
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(verified)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().VERIFIED_SIZE
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package xyz.jonesdev.sonar.common.update;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@UtilityClass
|
||||
public class UpdateChecker {
|
||||
private final ExecutorService ASYNC_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final LoggerWrapper LOGGER = new LoggerWrapper() {
|
||||
|
||||
@Override
|
||||
public void info(final String message, final Object... args) {
|
||||
Sonar.get().getLogger().info("[update-checker] " + message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(final String message, final Object... args) {
|
||||
Sonar.get().getLogger().warn("[update-checker] " + message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(final String message, final Object... args) {
|
||||
Sonar.get().getLogger().error("[update-checker] " + message, args);
|
||||
}
|
||||
};
|
||||
|
||||
public void checkForUpdates() {
|
||||
ASYNC_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
final URL url = new URL("https://api.github.com/repos/jonesdevelopment/sonar/releases/latest");
|
||||
final HttpsURLConnection urlConnection = prepareConnection(url);
|
||||
final JsonObject json = parseJson(urlConnection.getInputStream());
|
||||
final String latestStableVersion = json.get("tag_name").getAsString();
|
||||
|
||||
if (!latestStableVersion.equals(Sonar.get().getVersion().getSemanticVersion())) {
|
||||
LOGGER.warn("A new version of Sonar is available: {}", latestStableVersion);
|
||||
LOGGER.warn("Please make sure to update to the latest version to ensure stability and security:");
|
||||
LOGGER.warn("https://github.com/jonesdevelopment/sonar/releases");
|
||||
} else if (Sonar.get().getVersion().isOnMainBranch()) {
|
||||
LOGGER.info("You are currently using the latest stable release of Sonar!");
|
||||
} else {
|
||||
LOGGER.warn("You are currently using the latest development version of Sonar!");
|
||||
LOGGER.warn("The contributors of Sonar are not responsible for any damage done by using an unstable version" +
|
||||
".");
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
LOGGER.warn("Could not retrieve latest version information: {}", exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull JsonObject parseJson(final @NotNull InputStream inputStream) throws IOException {
|
||||
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
|
||||
final StringBuilder content = new StringBuilder();
|
||||
|
||||
// Read the entire page content
|
||||
try (final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
|
||||
String line;
|
||||
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
content.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse site content as JSON using Gson
|
||||
final JsonParser jsonParser = new JsonParser();
|
||||
return jsonParser.parse(content.toString()).getAsJsonObject();
|
||||
}
|
||||
|
||||
private @NotNull HttpsURLConnection prepareConnection(final @NotNull URL url) throws Exception {
|
||||
final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
// Set connection timeout
|
||||
urlConnection.setConnectTimeout(15000);
|
||||
urlConnection.setReadTimeout(15000);
|
||||
// Set necessary properties
|
||||
urlConnection.setRequestMethod("GET");
|
||||
urlConnection.setRequestProperty("Accept", "application/vnd.github.v3+json");
|
||||
// Only continue if the connection is a success
|
||||
if (urlConnection.getResponseCode() != 200) {
|
||||
throw new IllegalStateException("Unexpected response code " + urlConnection.getResponseCode());
|
||||
}
|
||||
return urlConnection;
|
||||
}
|
||||
}
|
||||
+24
-22
@@ -21,6 +21,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
@@ -33,7 +34,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// Mostly taken from
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
|
||||
@UtilityClass
|
||||
public class ProtocolUtil {
|
||||
@@ -47,40 +48,41 @@ public class ProtocolUtil {
|
||||
private static final String UNREGISTER_CHANNEL = "minecraft:unregister";
|
||||
private static final Pattern INVALID_IDENTIFIER_REGEX = Pattern.compile("[^a-z0-9\\-_]*");
|
||||
|
||||
public static @NotNull String readString(final ByteBuf buf) throws CorruptedFrameException {
|
||||
return readString(buf, Short.MAX_VALUE);
|
||||
public static @NotNull String readBrandMessage(final @NotNull ByteBuf content) throws DecoderException {
|
||||
final ByteBuf slice = content.slice();
|
||||
try {
|
||||
return readString(slice);
|
||||
} catch (DecoderException exception) {
|
||||
return readString(slice, 65536, slice.readableBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf buf,
|
||||
public static @NotNull String readString(final ByteBuf byteBuf) throws CorruptedFrameException {
|
||||
return readString(byteBuf, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf byteBuf,
|
||||
final int cap) throws CorruptedFrameException {
|
||||
return readString(buf, cap, false);
|
||||
final int length = VarIntUtil.readVarInt(byteBuf);
|
||||
return readString(byteBuf, cap, length);
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf buf,
|
||||
public static @NotNull String readString(final @NotNull ByteBuf byteBuf,
|
||||
final int cap,
|
||||
final boolean legacy) throws CorruptedFrameException {
|
||||
final int length = VarIntUtil.readVarInt(buf);
|
||||
|
||||
final int length) throws CorruptedFrameException {
|
||||
checkFrame(length >= 0, "Got a negative-length string");
|
||||
checkFrame(length <= cap * 3, "Bad string size");
|
||||
String str;
|
||||
if (legacy) {
|
||||
// TODO: length checking?
|
||||
str = buf.toString(StandardCharsets.UTF_8);
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
} else {
|
||||
checkFrame(buf.isReadable(length), "Got an invalid string length");
|
||||
str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
}
|
||||
checkFrame(byteBuf.isReadable(length), "Tried to read a too-long string");
|
||||
final String str = byteBuf.toString(byteBuf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
byteBuf.skipBytes(length);
|
||||
checkFrame(str.length() <= cap, "Got a too-long string");
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void writeString(final ByteBuf buf, final CharSequence str) {
|
||||
public static void writeString(final ByteBuf byteBuf, final CharSequence str) {
|
||||
final int size = ByteBufUtil.utf8Bytes(str);
|
||||
VarIntUtil.writeVarInt(buf, size);
|
||||
buf.writeCharSequence(str, StandardCharsets.UTF_8);
|
||||
VarIntUtil.writeVarInt(byteBuf, size);
|
||||
byteBuf.writeCharSequence(str, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static void writeUUID(final @NotNull ByteBuf byteBuf, final @NotNull UUID uuid) {
|
||||
|
||||
@@ -4,17 +4,34 @@ dependencies {
|
||||
compileOnly(project(":api"))
|
||||
compileOnly(project(":common"))
|
||||
|
||||
compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // Proxy module
|
||||
compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion")
|
||||
testCompileOnly("com.velocitypowered:velocity-proxy:$velocityVersion")
|
||||
|
||||
compileOnly("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
//annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
|
||||
testCompileOnly("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
testAnnotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
//testAnnotationProcessor("com.velocitypowered:velocity-api:$velocityVersion")
|
||||
|
||||
// Implement bStats.org for metrics
|
||||
implementation("org.bstats:bstats-velocity:3.0.2")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
val props = mapOf(
|
||||
"name" to rootProject.name,
|
||||
"version" to rootProject.version.toString().split("-")[0],
|
||||
"description" to "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)",
|
||||
"url" to "https://jonesdev.xyz/discord/",
|
||||
"main" to "xyz.jonesdev.sonar.velocity.SonarVelocityPlugin"
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("velocity-plugin.json") {
|
||||
expand(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -18,15 +18,15 @@
|
||||
package xyz.jonesdev.sonar.velocity;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.server.ServerWrapper;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
import xyz.jonesdev.sonar.velocity.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.velocity.command.VelocityInvocationSource;
|
||||
import xyz.jonesdev.sonar.velocity.command.VelocitySonarCommand;
|
||||
import xyz.jonesdev.sonar.velocity.fallback.FallbackListener;
|
||||
import xyz.jonesdev.sonar.velocity.verbose.VerboseWrapper;
|
||||
|
||||
@@ -72,30 +72,14 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
* <br>
|
||||
* We have to do this, so we can access all necessary API functions.
|
||||
*/
|
||||
public final ServerWrapper server = new ServerWrapper() {
|
||||
public final ServerWrapper server = new ServerWrapper(SonarPlatform.VELOCITY) {
|
||||
|
||||
@Override
|
||||
public SonarPlatform getPlatform() {
|
||||
return SonarPlatform.VELOCITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InvocationSender> getOnlinePlayer(final String username) {
|
||||
public Optional<InvocationSource> getOnlinePlayer(final String username) {
|
||||
return getPlugin().getServer().getAllPlayers().stream()
|
||||
.filter(player -> player.getUsername().equalsIgnoreCase(username))
|
||||
.findFirst()
|
||||
.map(player -> new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
player.sendMessage(Component.text(message));
|
||||
}
|
||||
});
|
||||
.map(VelocityInvocationSource::new);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,7 +90,7 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
getPlugin().getMetricsFactory().make(getPlugin(), getServer().getPlatform().getMetricsId());
|
||||
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new SonarCommand());
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getEventManager().register(getPlugin(), new FallbackListener(getFallback()));
|
||||
@@ -126,11 +110,4 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
.repeat(100L, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postReload() {
|
||||
|
||||
// Prepare cached messages
|
||||
FallbackListener.CachedMessages.update();
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,10 @@ import org.slf4j.Logger;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Getter
|
||||
@Plugin(id = "sonar",
|
||||
@Plugin(
|
||||
id = "sonar",
|
||||
name = "Sonar",
|
||||
version = "2.0.0",
|
||||
version = "$version",
|
||||
authors = {"Jones Development", "Sonar Contributors"},
|
||||
url = "https://jonesdev.xyz/discord/",
|
||||
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.velocity.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
|
||||
public final class VelocityInvocationSource extends InvocationSource {
|
||||
public VelocityInvocationSource(final @NotNull CommandSource sender) {
|
||||
super(sender instanceof Player
|
||||
? ((Player) sender).getUsername()
|
||||
: "Console", sender);
|
||||
}
|
||||
}
|
||||
+29
-86
@@ -21,14 +21,11 @@ import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSender;
|
||||
import xyz.jonesdev.sonar.api.command.SonarBaseCommand;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.SonarCommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
@@ -39,54 +36,22 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
static {
|
||||
CACHED_HELP_MESSAGE.addAll(Arrays.asList(
|
||||
Component.text("Running Sonar " + Sonar.get().getVersion()
|
||||
+ " on " + Sonar.get().getServer().getPlatform().getDisplayName()
|
||||
+ ".", NamedTextColor.YELLOW),
|
||||
Component.text("(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors", NamedTextColor.YELLOW),
|
||||
Component.text("https://github.com/jonesdevelopment/sonar", NamedTextColor.GREEN)
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar")),
|
||||
Component.empty(),
|
||||
Component.text("Need help or have any questions?", NamedTextColor.YELLOW),
|
||||
Component.textOfChildren(
|
||||
Component.text("Open a ticket on the Discord ", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open Discord)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/")),
|
||||
Component.text("or open a new issue on GitHub.", NamedTextColor.YELLOW)
|
||||
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open GitHub)")))
|
||||
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" +
|
||||
"/issues"))
|
||||
),
|
||||
Component.empty()
|
||||
));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> {
|
||||
var component = Component.textOfChildren(
|
||||
Component.text(" ▪ ", NamedTextColor.GRAY),
|
||||
Component.text("/sonar " + sub.getInfo().name(), NamedTextColor.GREEN),
|
||||
Component.text(" - ", NamedTextColor.GRAY),
|
||||
Component.text(sub.getInfo().description(), NamedTextColor.WHITE)
|
||||
);
|
||||
|
||||
component = component.clickEvent(
|
||||
ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sonar " + sub.getInfo().name() + " ")
|
||||
).hoverEvent(
|
||||
HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text(
|
||||
"§7Only players: §f" + (sub.getInfo().onlyPlayers() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Require console: §f" + (sub.getInfo().onlyConsole() ? "§a✔" : "§c✗")
|
||||
+ Sonar.LINE_SEPARATOR + "§7Permission: §f" + sub.getPermission()
|
||||
+ Sonar.LINE_SEPARATOR + "§7Aliases: §f" + sub.getAliases()
|
||||
))
|
||||
);
|
||||
CACHED_HELP_MESSAGE.add(component);
|
||||
});
|
||||
public final class VelocitySonarCommand implements SimpleCommand, SonarCommand {
|
||||
{
|
||||
cacheHelpMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final @NotNull Invocation invocation) {
|
||||
// Create our own invocation source wrapper to handle messages properly
|
||||
final InvocationSource invocationSource = new VelocityInvocationSource(invocation.source());
|
||||
|
||||
if (!(invocation.source() instanceof ConsoleCommandSource)) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!invocation.source().hasPermission("sonar.command")) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
@@ -96,16 +61,14 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
invocation.source().sendMessage(Component.text(Sonar.get().getConfig().COMMAND_COOL_DOWN));
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
invocation.source().sendMessage(Component.text(
|
||||
Sonar.get().getConfig().COMMAND_COOL_DOWN_LEFT
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))
|
||||
));
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,21 +77,6 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
final InvocationSender invocationSender = new InvocationSender() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return invocation.source() instanceof Player
|
||||
? ((Player) invocation.source()).getUsername()
|
||||
: "Console";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final String message) {
|
||||
invocation.source().sendMessage(Component.text(message));
|
||||
}
|
||||
};
|
||||
|
||||
if (invocation.arguments().length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream()
|
||||
@@ -142,10 +90,8 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !invocation.source().hasPermission(subcommand.get().getPermission())
|
||||
) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().SUB_COMMAND_NO_PERM
|
||||
.replace("%permission%", subcommand.get().getPermission())
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -153,25 +99,23 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
|
||||
subcommand.ifPresent(sub -> {
|
||||
if (sub.getInfo().onlyPlayers() && !(invocation.source() instanceof Player)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().PLAYERS_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.getInfo().onlyConsole() && !(invocation.source() instanceof ConsoleCommandSource)) {
|
||||
invocationSender.sendMessage(Sonar.get().getConfig().CONSOLE_ONLY);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly());
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSender, sub, invocation.arguments());
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, invocation.arguments());
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (sub.getInfo().arguments().length > 0
|
||||
&& commandInvocation.getRawArguments().length <= 1
|
||||
&& sub.getInfo().argumentsRequired()) {
|
||||
invocationSender.sendMessage(
|
||||
Sonar.get().getConfig().INCORRECT_COMMAND_USAGE
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")
|
||||
);
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,14 +128,18 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Object component : CACHED_HELP_MESSAGE) {
|
||||
invocation.source().sendMessage((Component) component);
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
invocationSource.sendMessage(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(final @NotNull Invocation invocation) {
|
||||
// Do not allow tab completion if the player does not have the required permission
|
||||
if (!invocation.source().hasPermission("sonar.command")) {
|
||||
return emptyList();
|
||||
}
|
||||
if (invocation.arguments().length <= 1) {
|
||||
if (TAB_SUGGESTIONS.isEmpty()) {
|
||||
for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) {
|
||||
@@ -221,9 +169,4 @@ public final class SonarCommand implements SimpleCommand, SonarBaseCommand {
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(final @NotNull Invocation invocation) {
|
||||
return invocation.source().hasPermission("sonar.command");
|
||||
}
|
||||
}
|
||||
+239
-149
@@ -21,7 +21,9 @@ import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
@@ -37,62 +39,71 @@ import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyJoinEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackTimeoutHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackVerificationHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccess;
|
||||
import xyz.jonesdev.sonar.common.fallback.traffic.TrafficChannelHooker;
|
||||
import xyz.jonesdev.sonar.velocity.SonarVelocity;
|
||||
import xyz.jonesdev.sonar.velocity.fallback.dummy.DummyConnection;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_HANDLER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.*;
|
||||
import static xyz.jonesdev.sonar.common.utility.geyser.GeyserUtil.isGeyserConnection;
|
||||
import static xyz.jonesdev.sonar.velocity.fallback.FallbackListener.CachedMessages.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackListener {
|
||||
private final @NotNull Fallback fallback;
|
||||
|
||||
public static class CachedMessages {
|
||||
static Component LOCKDOWN_DISCONNECT;
|
||||
static Component TOO_MANY_ONLINE_PER_IP;
|
||||
static Component TOO_MANY_PLAYERS;
|
||||
static Component BLACKLISTED;
|
||||
static Component ALREADY_VERIFYING;
|
||||
static Component ALREADY_QUEUED;
|
||||
static Component TOO_FAST_RECONNECT;
|
||||
public static Component INVALID_USERNAME;
|
||||
|
||||
public static void update() {
|
||||
ALREADY_VERIFYING = Component.text(Sonar.get().getConfig().ALREADY_VERIFYING);
|
||||
ALREADY_QUEUED = Component.text(Sonar.get().getConfig().ALREADY_QUEUED);
|
||||
TOO_MANY_PLAYERS = Component.text(Sonar.get().getConfig().TOO_MANY_PLAYERS);
|
||||
BLACKLISTED = Component.text(Sonar.get().getConfig().BLACKLISTED);
|
||||
TOO_FAST_RECONNECT = Component.text(Sonar.get().getConfig().TOO_FAST_RECONNECT);
|
||||
INVALID_USERNAME = Component.text(Sonar.get().getConfig().INVALID_USERNAME);
|
||||
TOO_MANY_ONLINE_PER_IP = Component.text(Sonar.get().getConfig().TOO_MANY_ONLINE_PER_IP);
|
||||
LOCKDOWN_DISCONNECT = Component.text(Sonar.get().getConfig().LOCKDOWN_DISCONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
private static final DummyConnection CLOSED_MINECRAFT_CONNECTION;
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
private static final MethodHandle INITIAL_CONNECTION;
|
||||
private static final MethodHandle CONNECTED_PLAYER;
|
||||
private static final Field CONNECTION_FIELD;
|
||||
private static Field SESSION_HANDLER_FIELD;
|
||||
|
||||
static {
|
||||
CLOSED_MINECRAFT_CONNECTION = new DummyConnection(null);
|
||||
|
||||
try {
|
||||
CONNECTED_PLAYER = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup())
|
||||
.findConstructor(ConnectedPlayer.class,
|
||||
MethodType.methodType(
|
||||
void.class,
|
||||
VelocityServer.class,
|
||||
GameProfile.class,
|
||||
MinecraftConnection.class,
|
||||
InetSocketAddress.class,
|
||||
boolean.class,
|
||||
IdentifiedKey.class
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
SESSION_HANDLER_FIELD = MinecraftConnection.class.getDeclaredField("activeSessionHandler");
|
||||
} catch (Throwable throwable) {
|
||||
// Velocity b266 changed the field name to "activeSessionHandler"
|
||||
SESSION_HANDLER_FIELD = MinecraftConnection.class.getDeclaredField("sessionHandler");
|
||||
}
|
||||
SESSION_HANDLER_FIELD.setAccessible(true);
|
||||
|
||||
CONNECTION_FIELD = InitialLoginSessionHandler.class.getDeclaredField("mcConnection");
|
||||
CONNECTION_FIELD.setAccessible(true);
|
||||
|
||||
@@ -117,84 +128,11 @@ public final class FallbackListener {
|
||||
public void handle(final @NotNull PreLoginEvent event) throws Throwable {
|
||||
Statistics.TOTAL_TRAFFIC.increment();
|
||||
|
||||
final InetAddress inetAddress = event.getConnection().getRemoteAddress().getAddress();
|
||||
|
||||
val inboundConnection = (LoginInboundConnection) event.getConnection();
|
||||
val initialConnection = (InitialInboundConnection) INITIAL_CONNECTION.invokeExact(inboundConnection);
|
||||
|
||||
final MinecraftConnection mcConnection = initialConnection.getConnection();
|
||||
final Channel channel = mcConnection.getChannel();
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
// Hook the custom traffic pipeline, so we can count the incoming and outgoing traffic
|
||||
TrafficChannelHooker.hook(pipeline, MINECRAFT_DECODER, MINECRAFT_ENCODER);
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (fallback.getBlacklisted().has(inetAddress.toString())) {
|
||||
markConnectionAsDead(mcConnection.getSessionHandler());
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
BLACKLISTED,
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getConfig().ENABLE_VERIFICATION) return;
|
||||
|
||||
// Check if the player is already verified.
|
||||
// No one wants to be verified over and over again.
|
||||
final GameProfile gameProfile = GameProfile.forOfflinePlayer(event.getUsername());
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, gameProfile.getId())) return;
|
||||
|
||||
// Completely skip Geyser connections (for now)
|
||||
if (isGeyserConnection(channel)) {
|
||||
// TODO: Do we need to log this?
|
||||
fallback.getLogger().info("Allowing Geyser connection: " + inetAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
// We now mark the connection as dead by using our dummy connection
|
||||
markConnectionAsDead(mcConnection.getSessionHandler());
|
||||
|
||||
// Check if Fallback is already verifying a player
|
||||
// → is another player with the same IP address connected to Fallback?
|
||||
if (fallback.getConnected().containsKey(event.getUsername())
|
||||
|| fallback.getConnected().containsValue(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
ALREADY_VERIFYING,
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot allow too many players on our Fallback server
|
||||
// There's technically no reason for limiting this, but we'll better stay safe.
|
||||
if (fallback.getConnected().size() > Sonar.get().getConfig().MAXIMUM_VERIFYING_PLAYERS) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
TOO_MANY_PLAYERS,
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!fallback.getRatelimiter().attempt(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
TOO_FAST_RECONNECT,
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (fallback.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
ALREADY_QUEUED,
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Run in the channel's event loop
|
||||
channel.eventLoop().execute(() -> {
|
||||
@@ -202,47 +140,204 @@ public final class FallbackListener {
|
||||
// Do not continue if the connection is closed or marked as disconnected
|
||||
if (mcConnection.isClosed() || mcConnection.isKnownDisconnect()) return;
|
||||
|
||||
// We have to add this pipeline to monitor whenever the client disconnects
|
||||
// to remove them from the list of connected and queued players
|
||||
pipeline.addFirst(FALLBACK_HANDLER, new FallbackChannelHandler(event.getUsername()));
|
||||
// Hook the custom traffic pipeline, so we can count the incoming and outgoing traffic
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
TrafficChannelHooker.hook(pipeline, MINECRAFT_DECODER, MINECRAFT_ENCODER);
|
||||
|
||||
// Queue the connection for further processing
|
||||
fallback.getQueue().queue(inetAddress, () -> channel.eventLoop().execute(() -> {
|
||||
final InetAddress inetAddress = event.getConnection().getRemoteAddress().getAddress();
|
||||
|
||||
// Do not continue if the connection is closed or marked as disconnected
|
||||
if (mcConnection.isClosed() || mcConnection.isKnownDisconnect()) return;
|
||||
try {
|
||||
// Backwards compatibility for Velocity b265 and below
|
||||
val activeSessionHandler = (MinecraftSessionHandler) SESSION_HANDLER_FIELD.get(mcConnection);
|
||||
|
||||
// Check if the username matches the valid name regex in order to prevent
|
||||
// UTF-16 names or other types of flood attacks
|
||||
if (!Sonar.get().getConfig().VALID_NAME_REGEX
|
||||
.matcher(event.getUsername()).matches()) {
|
||||
mcConnection.closeWith(Disconnect.create(INVALID_USERNAME, mcConnection.getProtocolVersion()));
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (fallback.getBlacklisted().has(inetAddress.toString())) {
|
||||
markConnectionAsDead(activeSessionHandler);
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
Sonar.get().getConfig().getVerification().getBlacklisted(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
pipeline.replace(
|
||||
READ_TIMEOUT,
|
||||
READ_TIMEOUT,
|
||||
new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().VERIFICATION_READ_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
);
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getConfig().getVerification().isEnabled()) return;
|
||||
|
||||
// We need to determine if the player is premium before we handle the connection,
|
||||
// so we can create a ConnectedPlayer instance without having to spoof this
|
||||
final boolean onlineMode = !event.getResult().isForceOfflineMode()
|
||||
&& (SonarVelocity.INSTANCE.getPlugin().getServer().getConfiguration().isOnlineMode()
|
||||
|| event.getResult().isOnlineModeAllowed());
|
||||
// Check if the player is already verified.
|
||||
// No one wants to be verified over and over again.
|
||||
final GameProfile gameProfile = GameProfile.forOfflinePlayer(event.getUsername());
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, gameProfile.getId())) return;
|
||||
|
||||
// Replace the session handler to intercept all packets and handle them
|
||||
mcConnection.setSessionHandler(new FallbackSessionHandler(
|
||||
fallback, mcConnection, inboundConnection,
|
||||
gameProfile, inetAddress, onlineMode
|
||||
));
|
||||
}));
|
||||
// Completely skip Geyser connections (for now)
|
||||
if (isGeyserConnection(channel)) {
|
||||
// TODO: Do we need to log this?
|
||||
fallback.getLogger().info("Allowing Geyser connection: {}", inetAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
// We now mark the connection as dead by using our fake connection
|
||||
markConnectionAsDead(activeSessionHandler);
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (fallback.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
Sonar.get().getConfig().getVerification().getAlreadyQueued(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Fallback is already verifying a player
|
||||
// → is another player with the same IP address connected to Fallback?
|
||||
if (fallback.getConnected().containsKey(event.getUsername())
|
||||
|| fallback.getConnected().containsValue(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
Sonar.get().getConfig().getVerification().getAlreadyVerifying(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot allow too many players on our Fallback server
|
||||
// There's technically no reason for limiting this, but we'll better stay safe.
|
||||
if (fallback.getConnected().size() > Sonar.get().getConfig().getVerification().getMaxVerifyingPlayers()) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
Sonar.get().getConfig().getVerification().getTooManyPlayers(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!fallback.getRatelimiter().attempt(inetAddress)) {
|
||||
initialConnection.getConnection().closeWith(Disconnect.create(
|
||||
Sonar.get().getConfig().getVerification().getTooFastReconnect(),
|
||||
inboundConnection.getProtocolVersion()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to add this pipeline to monitor whenever the client disconnects
|
||||
// to remove them from the list of connected and queued players
|
||||
pipeline.addFirst(FALLBACK_HANDLER, new FallbackChannelHandler(event.getUsername()));
|
||||
|
||||
// Queue the connection for further processing
|
||||
fallback.getQueue().queue(inetAddress, () -> channel.eventLoop().execute(() -> {
|
||||
|
||||
// Do not continue if the connection is closed or marked as disconnected
|
||||
if (mcConnection.isClosed() || mcConnection.isKnownDisconnect()) return;
|
||||
|
||||
// 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(event.getUsername()).matches()) {
|
||||
mcConnection.closeWith(Disconnect.create(Sonar.get().getConfig().getVerification().getInvalidUsername(),
|
||||
mcConnection.getProtocolVersion()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
pipeline.replace(
|
||||
READ_TIMEOUT,
|
||||
READ_TIMEOUT,
|
||||
new FallbackTimeoutHandler(Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
TimeUnit.MILLISECONDS)
|
||||
);
|
||||
|
||||
// We need to determine if the player is premium before we handle the connection,
|
||||
// so we can create a ConnectedPlayer instance without having to spoof this
|
||||
final boolean onlineMode = !event.getResult().isForceOfflineMode()
|
||||
&& (SonarVelocity.INSTANCE.getPlugin().getServer().getConfiguration().isOnlineMode()
|
||||
|| event.getResult().isOnlineModeAllowed());
|
||||
|
||||
// Don't allow exceptions or disconnect messages
|
||||
mcConnection.setAssociation(null);
|
||||
|
||||
// Create an instance for the connected player
|
||||
final ConnectedPlayer connectedPlayer;
|
||||
try {
|
||||
connectedPlayer = (ConnectedPlayer) CONNECTED_PLAYER.invokeExact(
|
||||
mcConnection.server,
|
||||
gameProfile,
|
||||
mcConnection,
|
||||
inboundConnection.getVirtualHost().orElse(null),
|
||||
onlineMode,
|
||||
inboundConnection.getIdentifiedKey()
|
||||
);
|
||||
} catch (Throwable throwable) {
|
||||
// This should not happen
|
||||
fallback.getLogger().error("Error processing {}: {}", gameProfile.getName(), throwable);
|
||||
mcConnection.close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance for the Fallback connection
|
||||
final FallbackUserWrapper fallbackPlayer = new FallbackUserWrapper(
|
||||
fallback, connectedPlayer, mcConnection, mcConnection.getChannel(),
|
||||
mcConnection.getChannel().pipeline(), inetAddress,
|
||||
ProtocolVersion.fromId(connectedPlayer.getProtocolVersion().getProtocol())
|
||||
);
|
||||
|
||||
// Disconnect if the protocol version could not be resolved
|
||||
if (fallbackPlayer.getProtocolVersion().isUnknown()) {
|
||||
fallbackPlayer.disconnect(Sonar.get().getConfig().getVerification().getInvalidProtocol());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already connected to the proxy but still tries to verify
|
||||
if (!mcConnection.server.canRegisterConnection(connectedPlayer)) {
|
||||
fallbackPlayer.disconnect(Sonar.get().getConfig().getVerification().getAlreadyConnected());
|
||||
return;
|
||||
}
|
||||
|
||||
// The player joined the verification
|
||||
Statistics.REAL_TRAFFIC.increment();
|
||||
|
||||
if (Sonar.get().getConfig().getVerification().isLogConnections()) {
|
||||
// Only log the processing message if the server isn't under attack.
|
||||
// We let the user override this through the configuration.
|
||||
if (!fallback.isPotentiallyUnderAttack() || Sonar.get().getConfig().getVerification().isLogDuringAttack()) {
|
||||
fallback.getLogger().info(Sonar.get().getConfig().getVerification().getConnectLog()
|
||||
.replace("%name%", connectedPlayer.getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(fallbackPlayer.getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(fallbackPlayer.getProtocolVersion().getProtocol())));
|
||||
}
|
||||
}
|
||||
|
||||
// Call the VerifyJoinEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(gameProfile.getName(), fallbackPlayer));
|
||||
|
||||
// Mark the player as connected → verifying players
|
||||
fallback.getConnected().put(gameProfile.getName(), inetAddress);
|
||||
|
||||
// This sometimes happens when the channel hangs, but the player is still connecting
|
||||
// This also fixes a weird issue with TCPShield and other reverse proxies
|
||||
if (fallbackPlayer.getPipeline().get(MINECRAFT_ENCODER) == null
|
||||
|| fallbackPlayer.getPipeline().get(MINECRAFT_DECODER) == null) {
|
||||
mcConnection.close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder encoder = new FallbackPacketEncoder(fallbackPlayer.getProtocolVersion());
|
||||
fallbackPlayer.getPipeline().replace(MINECRAFT_ENCODER, FALLBACK_PACKET_ENCODER, encoder);
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
fallbackPlayer.write(new LoginSuccess(gameProfile.getName(), gameProfile.getId()));
|
||||
|
||||
// The LoginSuccess packet has been sent, now we can change the registry state
|
||||
encoder.updateRegistry(fallbackPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0
|
||||
? FallbackPacketRegistry.CONFIG : FallbackPacketRegistry.GAME);
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
fallbackPlayer.getPipeline().replace(
|
||||
MINECRAFT_DECODER, FALLBACK_PACKET_DECODER, new FallbackPacketDecoder(fallbackPlayer,
|
||||
new FallbackVerificationHandler(fallbackPlayer, gameProfile.getName(), connectedPlayer.getUniqueId())
|
||||
));
|
||||
}));
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,27 +345,22 @@ public final class FallbackListener {
|
||||
public void handle(final @NotNull LoginEvent event) {
|
||||
val connectedPlayer = (ConnectedPlayer) event.getPlayer();
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_ENABLED) {
|
||||
if (!event.getPlayer().hasPermission("sonar.lockdown")) {
|
||||
if (Sonar.get().getConfig().getLockdown().isEnabled()) {
|
||||
if (!event.getPlayer().hasPermission(Sonar.get().getConfig().getLockdown().getBypassPermission())) {
|
||||
connectedPlayer.getConnection().closeWith(Disconnect.create(
|
||||
LOCKDOWN_DISCONNECT, connectedPlayer.getProtocolVersion()
|
||||
));
|
||||
Sonar.get().getConfig().getLockdown().getDisconnect(), connectedPlayer.getProtocolVersion()));
|
||||
|
||||
if (Sonar.get().getConfig().LOCKDOWN_LOG_ATTEMPTS) {
|
||||
Sonar.get().getLogger().info(
|
||||
Sonar.get().getConfig().LOCKDOWN_CONSOLE_LOG
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getRemoteAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getProtocolVersion().getProtocol()))
|
||||
);
|
||||
if (Sonar.get().getConfig().getLockdown().isLogAttempts()) {
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getLockdown().getConsoleLog()
|
||||
.replace("%player%", event.getPlayer().getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig()
|
||||
.formatAddress(event.getPlayer().getRemoteAddress().getAddress()))
|
||||
.replace("%protocol%",
|
||||
String.valueOf(event.getPlayer().getProtocolVersion().getProtocol())));
|
||||
}
|
||||
return;
|
||||
} else if (Sonar.get().getConfig().LOCKDOWN_ENABLE_NOTIFY) {
|
||||
event.getPlayer().sendMessage(
|
||||
Component.text(Sonar.get().getConfig().LOCKDOWN_NOTIFICATION)
|
||||
);
|
||||
} else if (Sonar.get().getConfig().getLockdown().isNotifyAdmins()) {
|
||||
event.getPlayer().sendMessage(Component.text(Sonar.get().getConfig().getLockdown().getNotification()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +368,7 @@ public final class FallbackListener {
|
||||
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().MAXIMUM_ONLINE_PER_IP;
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
|
||||
if (maxOnlinePerIp > 0) {
|
||||
final long onlinePerIp = SonarVelocity.INSTANCE.getPlugin().getServer().getAllPlayers().stream()
|
||||
@@ -288,7 +378,7 @@ public final class FallbackListener {
|
||||
// We use '>=' because the player connecting to the server hasn't joined yet
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
connectedPlayer.getConnection().closeWith(Disconnect.create(
|
||||
TOO_MANY_ONLINE_PER_IP, connectedPlayer.getProtocolVersion()
|
||||
Sonar.get().getConfig().getTooManyOnlinePerIp(), connectedPlayer.getProtocolVersion()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.velocity.fallback;
|
||||
|
||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackVerificationHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_DECODER;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_PACKET_ENCODER;
|
||||
|
||||
@Getter
|
||||
public final class FallbackSessionHandler implements MinecraftSessionHandler {
|
||||
private final Fallback fallback;
|
||||
private final MinecraftConnection mcConnection;
|
||||
private final LoginInboundConnection inboundConnection;
|
||||
|
||||
private static final MethodHandle CONNECTED_PLAYER;
|
||||
|
||||
static {
|
||||
try {
|
||||
CONNECTED_PLAYER = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup())
|
||||
.findConstructor(ConnectedPlayer.class,
|
||||
MethodType.methodType(
|
||||
void.class,
|
||||
VelocityServer.class,
|
||||
GameProfile.class,
|
||||
MinecraftConnection.class,
|
||||
InetSocketAddress.class,
|
||||
boolean.class,
|
||||
IdentifiedKey.class
|
||||
)
|
||||
);
|
||||
} catch (Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
FallbackSessionHandler(final Fallback fallback,
|
||||
final @NotNull MinecraftConnection mcConnection,
|
||||
final LoginInboundConnection inboundConnection,
|
||||
final GameProfile gameProfile,
|
||||
final InetAddress inetAddress,
|
||||
final boolean onlineMode) {
|
||||
this.fallback = fallback;
|
||||
this.mcConnection = mcConnection;
|
||||
this.inboundConnection = inboundConnection;
|
||||
|
||||
// Don't allow exceptions or disconnect messages
|
||||
mcConnection.setAssociation(null);
|
||||
|
||||
// Create an instance for the connected player
|
||||
final ConnectedPlayer connectedPlayer;
|
||||
try {
|
||||
connectedPlayer = (ConnectedPlayer) CONNECTED_PLAYER.invokeExact(
|
||||
mcConnection.server,
|
||||
gameProfile,
|
||||
mcConnection,
|
||||
inboundConnection.getVirtualHost().orElse(null),
|
||||
onlineMode,
|
||||
inboundConnection.getIdentifiedKey()
|
||||
);
|
||||
} catch (Throwable throwable) {
|
||||
// This should not happen
|
||||
fallback.getLogger().error("Error processing {}: {}", gameProfile.getName(), throwable);
|
||||
mcConnection.close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance for the Fallback connection
|
||||
final FallbackPlayerWrapper fallbackPlayer = new FallbackPlayerWrapper(
|
||||
fallback, connectedPlayer, mcConnection, mcConnection.getChannel(),
|
||||
mcConnection.getChannel().pipeline(), inetAddress,
|
||||
ProtocolVersion.fromId(connectedPlayer.getProtocolVersion().getProtocol())
|
||||
);
|
||||
|
||||
// Check if the player is already connected to the proxy
|
||||
if (!mcConnection.server.canRegisterConnection(connectedPlayer)) {
|
||||
fallbackPlayer.disconnect("Could not find any available servers.");
|
||||
return;
|
||||
}
|
||||
|
||||
// The player joined the verification
|
||||
Statistics.REAL_TRAFFIC.increment();
|
||||
|
||||
if (Sonar.get().getConfig().LOG_CONNECTIONS) {
|
||||
// Only log the processing message if the server isn't under attack.
|
||||
// We let the user override this through the configuration.
|
||||
if (!fallback.isPotentiallyUnderAttack() || Sonar.get().getConfig().LOG_DURING_ATTACK) {
|
||||
fallback.getLogger().info(Sonar.get().getConfig().VERIFICATION_CONNECT_LOG
|
||||
.replace("%name%", connectedPlayer.getUsername())
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(fallbackPlayer.getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(fallbackPlayer.getProtocolVersion().getProtocol())));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the player as connected → verifying players
|
||||
fallback.getConnected().put(gameProfile.getName(), inetAddress);
|
||||
|
||||
// This sometimes happens when the channel hangs, but the player is still connecting
|
||||
// This also fixes a weird issue with TCPShield and other reverse proxies
|
||||
if (fallbackPlayer.getPipeline().get(MINECRAFT_ENCODER) == null
|
||||
|| fallbackPlayer.getPipeline().get(MINECRAFT_DECODER) == null) {
|
||||
mcConnection.close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
fallbackPlayer.getPipeline().replace(
|
||||
MINECRAFT_ENCODER,
|
||||
FALLBACK_PACKET_ENCODER,
|
||||
new FallbackPacketEncoder(fallbackPlayer.getProtocolVersion())
|
||||
);
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
fallbackPlayer.getPipeline().replace(
|
||||
MINECRAFT_DECODER,
|
||||
FALLBACK_PACKET_DECODER,
|
||||
new FallbackPacketDecoder(
|
||||
fallbackPlayer,
|
||||
new FallbackVerificationHandler(
|
||||
fallbackPlayer,
|
||||
gameProfile.getName(),
|
||||
connectedPlayer.getUniqueId()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(final ByteBuf buf) {
|
||||
mcConnection.close(true);
|
||||
}
|
||||
}
|
||||
+12
-8
@@ -19,25 +19,26 @@ package xyz.jonesdev.sonar.velocity.fallback;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackPlayer;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.Disconnect;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.Disconnect;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Getter
|
||||
@ToString(of = {"protocolVersion"})
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackPlayerWrapper implements FallbackPlayer<ConnectedPlayer, MinecraftConnection> {
|
||||
public final class FallbackUserWrapper implements FallbackUser<ConnectedPlayer, MinecraftConnection> {
|
||||
private final Fallback fallback;
|
||||
private final ConnectedPlayer player;
|
||||
private final MinecraftConnection connection;
|
||||
@@ -47,14 +48,17 @@ public final class FallbackPlayerWrapper implements FallbackPlayer<ConnectedPlay
|
||||
private final ProtocolVersion protocolVersion;
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull String reason) {
|
||||
final String serialized = ProtocolUtils.getJsonChatSerializer(connection.getProtocolVersion())
|
||||
.serialize(Component.text(reason));
|
||||
connection.closeWith(Disconnect.create(serialized));
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final @NotNull Object packet) {
|
||||
connection.write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayedWrite(final @NotNull Object packet) {
|
||||
connection.delayedWrite(packet);
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -27,9 +27,7 @@ public final class VerboseWrapper extends Verbose {
|
||||
private final ProxyServer server;
|
||||
|
||||
@Override
|
||||
public void broadcast(final String message) {
|
||||
final Component component = Component.text(message);
|
||||
|
||||
public void broadcast(final Component component) {
|
||||
synchronized (subscribers) {
|
||||
for (final String subscriber : subscribers) {
|
||||
server.getPlayer(subscriber).ifPresent(player -> {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "sonar",
|
||||
"name": "$name",
|
||||
"version": "$version",
|
||||
"description": "$description",
|
||||
"url": "$url",
|
||||
"authors": [
|
||||
"Jones Development",
|
||||
"Sonar Contributors"
|
||||
],
|
||||
"dependencies": [],
|
||||
"main": "$main"
|
||||
}
|
||||
Reference in new issue
Block a user