Compare commits

...
24 Commits
Author SHA1 Message Date
Jones e33aac29c5 Release version 2.0.1 (#29) 2023-09-30 09:15:05 +02:00
Jones 42dc3af6c9 Implement unofficial branch warnings and improve version management (#28)
* feat: check for non-main-branch builds and mark them as experimental

* feat: implement Implementation-Vendor

* feat: store version information in gradle.properties

* fix(bungee, bukkit): only use the semantic version for the plugin.yml/bungee.yml
2023-09-27 16:51:12 +02:00
Jones ff402d5096 Fix configuration reset after InvalidConfigurationException (#27)
* fix: only create a new messages configuration object if the preferred language changed (https://github.com/jonesdevelopment/sonar/issues/26)

* feat(api): better exception handling when loading the configuration

* fix: only load the configuration if the file already exists (https://github.com/jonesdevelopment/sonar/issues/26)

* feat: create a backup of the config if something goes wrong (https://github.com/jonesdevelopment/sonar/issues/26)

* fix: config header not showing and options not applying
2023-09-27 16:13:12 +02:00
Jones 06a72bf3bd Implement simple event system (#25)
* refactor(api): use toString() instead of formattedDelay() for SystemTimer

* fix(api): actually use the elapsed() function in SystemTimer

* feat(api): implement simple event system

* refactor(api): FallbackPlayer -> FallbackUser
2023-09-25 23:40:34 +02:00
Jones 3fb4291163 Merge pull request #24 from jonesdevelopment/create-code-of-conduct
Create CODE_OF_CONDUCT.md
2023-09-25 22:03:08 +02:00
Jones cc57b1d978 Create CODE_OF_CONDUCT.md 2023-09-25 22:02:41 +02:00
Jones 750b42b5ab Merge pull request #23 from jonesdevelopment/dependabot/gradle/org.projectlombok-lombok-1.18.30
build(deps): bump org.projectlombok:lombok from 1.18.28 to 1.18.30
2023-09-25 21:54:40 +02:00
Jones ad017024ae Merge pull request #22 from jonesdevelopment/dependabot/gradle/io.netty-netty-all-4.1.98.Final
build(deps): bump io.netty:netty-all from 4.1.97.Final to 4.1.98.Final
2023-09-25 21:54:19 +02:00
dependabot[bot] 5887d82c19 build(deps): bump org.projectlombok:lombok from 1.18.28 to 1.18.30
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.28 to 1.18.30.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.28...v1.18.30)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 01:06:35 +00:00
dependabot[bot] b1e5d01062 build(deps): bump io.netty:netty-all from 4.1.97.Final to 4.1.98.Final
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.97.Final to 4.1.98.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.97.Final...netty-4.1.98.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 01:06:26 +00:00
Jones a50bf45176 Merge pull request #21 from jonesdevelopment/fix/1.7-support
Implement Minecraft 1.7 support
2023-09-23 11:07:39 +02:00
Michel Elkenwaat e0b5ad2a33 feat(fallback): implement UpdateSectionBlocks packet for 1.7 (https://github.com/jonesdevelopment/sonar/issues/20) 2023-09-23 11:01:32 +02:00
Michel Elkenwaat e5ee94362c fix(fallback): false positive with new fell too fast check 2023-09-23 10:54:01 +02:00
Michel Elkenwaat d877568b38 fix(fallback): 1.7 support for PositionLook packet 2023-09-23 10:53:27 +02:00
Michel Elkenwaat 0edec77135 fix(fallback): recode PositionLook packet (encode) 2023-09-23 01:03:01 +02:00
Michel Elkenwaat 54445cdfdb fix(fallback): fix EmptyChunkData packet being broken 2023-09-23 00:56:23 +02:00
Michel Elkenwaat 8545d60791 fix(fallback): gravity check being broken 2023-09-23 00:52:02 +02:00
Michel Elkenwaat 11327e2890 fix(fallback): 1.7-1.8.9 potentially unloaded chunks causing issues 2023-09-23 00:21:00 +02:00
Michel Elkenwaat ad511836f3 fix: exclude changedBlocks from toString() in UpdateSectionBlocks packet 2023-09-23 00:13:57 +02:00
Michel Elkenwaat 5cb79d3acf fix: implement 1.7 support to DefaultSpawnPosition packet 2023-09-23 00:08:55 +02:00
Michel Elkenwaat 032dd4f94e feat(fallback): implement packet limits to Position/PositionLook 2023-09-23 00:03:06 +02:00
Michel Elkenwaat e831c1a646 fix: remove leftover debug messages 2023-09-23 00:00:17 +02:00
Michel Elkenwaat f50c90a690 fix: KeepAlive state check now ignores 1.7-1.8.9 2023-09-22 23:57:45 +02:00
Michel Elkenwaat 1e7e4ba5f1 fix(fallback): Position and PositionLook packet not decoding correctly 2023-09-22 23:57:18 +02:00
37 changed files with 663 additions and 159 deletions

No files matched your search

+128
View File
@@ -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.
+7 -9
View File
@@ -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,13 +32,13 @@ 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
compileOnly("io.netty:netty-all:4.1.98.Final") // netty
implementation("net.kyori:adventure-nbt:4.14.0") // nbt
implementation("com.j256.ormlite:ormlite-jdbc:6.1") // ORM
@@ -62,6 +59,7 @@ 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"
}
}
+2
View File
@@ -0,0 +1,2 @@
group=xyz.jonesdev.sonar
version=2.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;
@@ -58,6 +59,11 @@ public interface Sonar {
*/
void reload();
@NotNull
default SonarEventManager getEventManager() {
return SonarEventManager.INSTANCE;
}
@NotNull
default SonarVersion getVersion() {
return SonarVersion.GET;
@@ -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 + ")";
}
@@ -20,15 +20,21 @@ package xyz.jonesdev.sonar.api.config;
import lombok.Getter;
import org.simpleyaml.configuration.comments.format.YamlCommentFormat;
import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.exceptions.InvalidConfigurationException;
import xyz.jonesdev.sonar.api.Sonar;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
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(
@@ -48,6 +54,7 @@ public final class SimpleYamlConfig {
throw new IllegalStateException("Could not create folder?!");
}
this.file = file;
this.yaml = new YamlFile(file.getPath());
}
@@ -60,14 +67,42 @@ public final class SimpleYamlConfig {
}
public void load() {
String backupContent = null;
try {
// https://github.com/jonesdevelopment/sonar/issues/26
// Only load the configuration if the file already exists
if (yaml.exists()) {
try {
// Load all lines from the existing files before trying to load/overwrite it
backupContent = String.join(LINE_SEPARATOR, Files.readAllLines(file.toPath()));
} catch (IOException exception) {
Sonar.get().getLogger().error("Error while creating backup configuration: {}", exception);
}
yaml.loadWithComments();
} else {
yaml.createOrLoadWithComments();
}
// Always load the comment format and options
yaml.setCommentFormat(YamlCommentFormat.PRETTY);
yaml.createOrLoadWithComments();
yaml.options().headerFormatter().commentPrefix("# ");
yaml.setHeader(String.join(Sonar.LINE_SEPARATOR, HEADER));
yaml.setHeader(String.join(LINE_SEPARATOR, HEADER));
} catch (InvalidConfigurationException exception) {
Sonar.get().getLogger().error("Invalid configuration: {}", exception);
// Try to save a backup configuration to prevent data loss
if (backupContent != null) {
final File backupFile = new File(file.getParentFile(), file.getName() + ".broken");
try (final FileWriter fileWriter = new FileWriter(backupFile, false)) {
fileWriter.write(backupContent);
Sonar.get().getLogger().info("Saved backup configuration to {}", backupFile.getName());
} catch (Throwable throwable) {
Sonar.get().getLogger().warn("Could not load from backup configuration: {}", throwable);
}
}
} catch (IOException exception) {
exception.printStackTrace(System.err);
Sonar.get().getLogger().error("Error while loading configuration: {}", exception);
}
}
@@ -198,7 +198,11 @@ public final class SonarConfiguration {
DatabaseType.valueOf(generalConfig.getString("general.database.type", DatabaseType.NONE.name()).toUpperCase());
// Message settings
messagesConfig = new SimpleYamlConfig(pluginFolder, "lang/" + LANGUAGE);
// Only create a new messages configuration object if the preferred language changed
// https://github.com/jonesdevelopment/sonar/issues/26
if (messagesConfig == null || !messagesConfig.getFile().getName().equals(LANGUAGE + ".yml")) {
messagesConfig = new SimpleYamlConfig(pluginFolder, "lang/" + LANGUAGE);
}
messagesConfig.load();
messagesConfig.getYaml().setComment("messages.prefix",
@@ -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();
@@ -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 {
}
@@ -0,0 +1,29 @@
/*
* 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;
@FunctionalInterface
public interface SonarEventListener {
/**
* Handles the publishing of an event
*
* @param event [Cancellable] Sonar event
*/
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;
}
@@ -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 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;
}
@@ -24,13 +24,15 @@ 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();
@@ -72,6 +74,9 @@ public interface FallbackPlayer<X, Y> {
*/
default void fail(final @Nullable String reason) {
if (getChannel().isActive()) {
// Call the VerifyFailedEvent for external API usage
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(this, reason));
disconnect(Sonar.get().getConfig().VERIFICATION_FAILED);
if (reason != null) {
@@ -89,6 +94,9 @@ 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
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
@@ -35,7 +35,8 @@ public final class SystemTimer {
return delay() >= amount;
}
public String formattedDelay() {
@Override
public String toString() {
return String.format("%.3f", delay() / 1000D);
}
}
@@ -47,7 +47,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;
+1 -1
View File
@@ -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/"
+1 -1
View File
@@ -6,7 +6,7 @@ apply(plugin = "net.minecrell.plugin-yml.bungee")
bungee {
name = rootProject.name
version = rootProject.version.toString()
version = rootProject.version.toString().split("-")[0]
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
author = "Jones Development, Sonar Contributors"
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
@@ -26,7 +26,7 @@ import net.md_5.bungee.api.chat.TextComponent;
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;
@@ -35,7 +35,7 @@ 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;
@@ -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())
@@ -63,13 +63,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,7 +84,7 @@ 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);
@@ -24,7 +24,8 @@ import lombok.RequiredArgsConstructor;
import lombok.Setter;
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;
@@ -41,7 +42,7 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
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;
@@ -72,10 +73,10 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
private final boolean canMove;
}
public FallbackVerificationHandler(final @NotNull FallbackPlayer<?, ?> player,
public FallbackVerificationHandler(final @NotNull FallbackUser<?, ?> user,
final @NotNull String username,
final @NotNull UUID uuid) {
this.player = player;
this.user = user;
this.username = username;
this.uuid = uuid;
this.transactionId = (short) RANDOM.nextInt();
@@ -83,14 +84,14 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
this.state = State.KEEP_ALIVE;
// Send LoginSuccess packet to make the client think they are joining the server
player.write(new ServerLoginSuccess(username, uuid));
user.write(new ServerLoginSuccess(username, uuid));
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
// 1.7 players don't have KeepAlive packets in the login process
sendJoinGamePacket();
} else {
// Send first KeepAlive to check if the connection is somewhat responsive
player.write(new KeepAlive(verifyKeepAliveId));
user.write(new KeepAlive(verifyKeepAliveId));
}
}
@@ -99,7 +100,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// and go on with the flow of the verification.
state = State.TRANSACTION;
// Send a transaction with a
player.write(new Transaction(
user.write(new Transaction(
0, transactionId, false
));
}
@@ -109,7 +110,7 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// 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()));
}
private void sendChunkData() {
@@ -117,38 +118,36 @@ 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);
user.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.write(UPDATE_SECTION_BLOCKS);
}
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;
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 {
// 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);
// TODO: fix this?
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) return true;
// No need to check for empty or too long client brands since
// ProtocolUtil#readString already does exactly that.
final String read = ProtocolUtil.readString(content, Sonar.get().getConfig().MAXIMUM_BRAND_LENGTH);
// Regex pattern for validating client brands
final Pattern pattern = Sonar.get().getConfig().VALID_BRAND_REGEX;
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;
}
@@ -164,17 +163,20 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Check for timeout since the player could be sending packets but not important ones
final long timeout = Sonar.get().getConfig().VERIFICATION_TIMEOUT;
// 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 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");
// The correct KeepAlive packet has been received
@@ -185,7 +187,7 @@ 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) {
@@ -203,11 +205,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// 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) {
@@ -235,12 +237,12 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
}
// Make sure the player is unable to fly (the player is in spectator mode)
player.write(DEFAULT_ABILITIES);
user.write(DEFAULT_ABILITIES);
// Teleport the player to the spawn position
player.write(SPAWN_TELEPORT);
user.write(SPAWN_TELEPORT);
// 1.7-1.8 clients do not have a TeleportConfirm packet
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) <= 0) {
// Immediately send the chunk data as 1.7-1.8 can't confirm teleports
sendChunkData();
} else {
@@ -304,9 +306,8 @@ 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
// Check if the chunk might be unloaded
if (deltaY > 0.07) {
// Verify the player if they sent correct movement packets
if (movementTick++ >= MAX_MOVEMENT_TICK) {
if (Sonar.get().getConfig().CHECK_COLLISIONS) {
@@ -334,26 +335,27 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
// Checking collisions is disabled, just finish verification
finish();
}
} else if (y >= DEFAULT_Y_COLLIDE_POSITION && y <= DYNAMIC_SPAWN_Y_POSITION) {
} else if (y <= DYNAMIC_SPAWN_Y_POSITION && y >= DEFAULT_Y_COLLIDE_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);
// Check if the y motion is roughly equal to the predicted value
checkFrame(offsetY < 0.01, "invalid offset: " + y + ", " + offsetY);
final String verbose = String.format("%d: %.7f %.10f %.10f!%.10f", movementTick, y, offsetY, deltaY, predictedY);
checkFrame(offsetY < 0.01, verbose);
}
}
}
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 +363,24 @@ 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()
user.getInetAddress().toString(), uuid, 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, uuid, user, login.delay()));
player.getFallback().getLogger().info(
// Disconnect player with the verification success message
user.disconnect(Sonar.get().getConfig().VERIFICATION_SUCCESS);
user.getFallback().getLogger().info(
Sonar.get().getConfig().VERIFICATION_SUCCESSFUL_LOG
.replace("%name%", username)
.replace("%time%", login.formattedDelay())
.replace("%time%", login.toString())
);
}
}
@@ -23,21 +23,21 @@ 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.common.utility.protocol.VarIntUtil.readVarInt;
@RequiredArgsConstructor
public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
private final FallbackPlayer<?, ?> connection;
private final FallbackUser<?, ?> user;
private final FallbackPacketRegistry.ProtocolRegistry registry;
private final FallbackPacketListener listener;
public FallbackPacketDecoder(final @NotNull FallbackPlayer<?, ?> connection,
public FallbackPacketDecoder(final @NotNull FallbackUser<?, ?> user,
final @NotNull FallbackPacketListener listener) {
this.connection = connection;
this.user = user;
this.registry = FallbackPacketRegistry.SONAR.getProtocolRegistry(
FallbackPacketRegistry.Direction.SERVERBOUND, connection.getProtocolVersion()
FallbackPacketRegistry.Direction.SERVERBOUND, user.getProtocolVersion()
);
this.listener = listener;
}
@@ -64,14 +64,14 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
doLengthSanityChecks(byteBuf, packet);
try {
packet.decode(byteBuf, connection.getProtocolVersion());
packet.decode(byteBuf, user.getProtocolVersion());
} catch (Throwable throwable) {
connection.fail("could not decode packet");
user.fail("could not decode packet");
throw new CorruptedFrameException("Failed to decode packet");
}
if (byteBuf.isReadable()) {
connection.fail("could not read packet to end");
user.fail("could not read packet to end (" + byteBuf.readableBytes() + " bytes left)");
throw new CorruptedFrameException("Could not read packet to end");
}
@@ -85,15 +85,15 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
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");
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");
throw new CorruptedFrameException("Packet too small");
}
}
@@ -123,7 +123,7 @@ public enum FallbackPacketRegistry {
map(0x20, MINECRAFT_1_19_3, true),
map(0x24, MINECRAFT_1_19_4, true));
clientbound.register(UpdateSectionBlocks.class, UpdateSectionBlocks::new,
map(34, MINECRAFT_1_7_2, true),
map(0x22, MINECRAFT_1_7_2, true),
map(16, MINECRAFT_1_9, true),
map(15, MINECRAFT_1_13, true),
map(16, MINECRAFT_1_15, true),
@@ -122,7 +122,7 @@ public class FallbackPreparer {
8, DYNAMIC_SPAWN_Y_POSITION, 8,
0f, 0f,
RANDOM.nextInt(Short.MAX_VALUE),
true
false // 1.7 support
);
// Prepare collision platform positions
@@ -38,14 +38,20 @@ public final class DefaultSpawnPosition implements FallbackPacket {
@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);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) {
byteBuf.writeInt(x);
byteBuf.writeInt(y);
byteBuf.writeInt(z);
} else {
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);
byteBuf.writeLong(encoded);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
byteBuf.writeFloat(angle);
if (protocolVersion.compareTo(MINECRAFT_1_17) >= 0) {
byteBuf.writeFloat(angle);
}
}
}
@@ -48,10 +48,20 @@ public final class EmptyChunkData implements FallbackPacket {
private static final byte[] LEGACY_FILLER_BYTES = new byte[256];
private static final byte[] MODERN_FILLER_BYTES = new byte[1024];
private static final long[] MASK;
// Prepare nbt for 1.18 and pre-1.18
private static final CompoundBinaryTag MODERN_TAG, LEGACY_TAG;
static {
final BitSet bitSet = new BitSet();
for (int i = 0; i < 16; i++) {
bitSet.set(i, false);
}
MASK = bitSet.toLongArray();
MODERN_TAG = prepareNBT(false);
LEGACY_TAG = prepareNBT(true);
}
@@ -73,32 +83,26 @@ public final class EmptyChunkData implements FallbackPacket {
byteBuf.writeInt(x);
byteBuf.writeInt(z);
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
byteBuf.writeBoolean(true);
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) <= 0) {
writeVarInt(byteBuf, MASK.length);
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0 && protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
byteBuf.writeBoolean(true);
}
for (final long l : MASK) {
byteBuf.writeLong(l);
}
}
} else {
byteBuf.writeBoolean(true); // Full chunk
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
if (protocolVersion.compareTo(MINECRAFT_1_8) == 0) {
byteBuf.writeShort(1);
} else {
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0
&& protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16_2) < 0) {
byteBuf.writeBoolean(true); // Ignore old data
}
if (protocolVersion.compareTo(ProtocolVersion.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 +125,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(new byte[2]); // 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) {
@@ -36,6 +36,10 @@ public final class Position implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final 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();
@@ -45,4 +49,14 @@ public final class Position implements FallbackPacket {
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public int expectedMaxLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
return protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 33 : 25;
}
@Override
public int expectedMinLength(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
return 25;
}
}
@@ -41,6 +41,10 @@ public final class PositionLook implements FallbackPacket {
@Override
public void decode(final ByteBuf byteBuf, final 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();
@@ -55,14 +59,30 @@ public final class PositionLook implements FallbackPacket {
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;
}
}
@@ -41,7 +41,7 @@ public final class Transaction implements FallbackPacket {
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);
@@ -26,15 +26,14 @@ 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;
@@ -44,11 +43,22 @@ public final class UpdateSectionBlocks implements FallbackPacket {
if (protocolVersion.compareTo(MINECRAFT_1_16_2) < 0) {
byteBuf.writeInt(sectionX);
byteBuf.writeInt(sectionZ);
writeVarInt(byteBuf, changedBlocks.length);
for (final ChangedBlock block : changedBlocks) {
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
writeVarInt(byteBuf, block.getType().getId(protocolVersion));
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
byteBuf.writeShort((short) changedBlocks.length);
byteBuf.writeInt(4 * changedBlocks.length);
for (final ChangedBlock block : changedBlocks) {
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
byteBuf.writeShort(block.getType().getId(protocolVersion));
}
} else {
writeVarInt(byteBuf, changedBlocks.length);
for (final ChangedBlock block : changedBlocks) {
byteBuf.writeShort(block.getLegacyChunkPosCrammed());
writeVarInt(byteBuf, block.getType().getId(protocolVersion));
}
}
} else {
final ChangedBlock lastChangedBlock = changedBlocks[changedBlocks.length - 1];
@@ -53,34 +53,21 @@ public class ProtocolUtil {
public static @NotNull String readString(final ByteBuf buf,
final int cap) throws CorruptedFrameException {
return readString(buf, cap, false);
}
public static @NotNull String readString(final ByteBuf buf,
final int cap,
final boolean legacy) throws CorruptedFrameException {
final int length = VarIntUtil.readVarInt(buf);
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(buf.isReadable(length), "Got an invalid string length");
final String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.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) {
@@ -33,7 +33,7 @@ import java.nio.file.Path;
@Getter
@Plugin(id = "sonar",
name = "Sonar",
version = "2.0.0",
version = "2.0.1",
authors = {"Jones Development", "Sonar Contributors"},
url = "https://jonesdev.xyz/discord/",
description = "Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)"
@@ -29,6 +29,7 @@ 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.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;
@@ -106,7 +107,7 @@ public final class FallbackSessionHandler implements MinecraftSessionHandler {
}
// Create an instance for the Fallback connection
final FallbackPlayerWrapper fallbackPlayer = new FallbackPlayerWrapper(
final FallbackUserWrapper fallbackPlayer = new FallbackUserWrapper(
fallback, connectedPlayer, mcConnection, mcConnection.getChannel(),
mcConnection.getChannel().pipeline(), inetAddress,
ProtocolVersion.fromId(connectedPlayer.getProtocolVersion().getProtocol())
@@ -132,6 +133,9 @@ public final class FallbackSessionHandler implements MinecraftSessionHandler {
}
}
// 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);
@@ -25,19 +25,21 @@ 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 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;