diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 7ae5ed1b..408bfc92 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -26,25 +26,14 @@ jobs:
# Pull changes
- uses: actions/checkout@v2.3.4
- # Cache artifacts - however this has the downside that we don't get notified of
- # artifact resolution failures like invalid repository
- # Nevertheless the repositories should be more stable and it makes no sense to pull
- # a same version every time
- # A dry run would make more sense
- - uses: actions/cache@v2.1.4
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
# Setup Java
- name: Set up JDK
- uses: actions/setup-java@v2.1.0
+ uses: actions/setup-java@v2.3.0
with:
distribution: 'adopt'
# Use Java 11, because it's minimum required version
java-version: 11
+ cache: 'maven'
# Build and test (included in package)
- name: Build with Maven and test
diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml
index 8e048961..4c89a7c4 100644
--- a/core/src/main/resources/config.yml
+++ b/core/src/main/resources/config.yml
@@ -252,6 +252,7 @@ database: '{pluginDir}/FastLogin.db'
# MySQL/MariaDB
# If you want to enable it uncomment only the lines below this not this line.
+# If on velocity use 'fastlogin.mysql.cj.jdbc.Driver' as driver
#driver: 'com.mysql.jdbc.Driver'
#host: '127.0.0.1'
#port: 3306
diff --git a/pom.xml b/pom.xml
index 6031c250..f3971014 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,7 @@
core
bukkit
bungee
+ velocity
diff --git a/velocity/pom.xml b/velocity/pom.xml
new file mode 100644
index 00000000..64582e85
--- /dev/null
+++ b/velocity/pom.xml
@@ -0,0 +1,133 @@
+
+
+ 4.0.0
+
+
+ com.github.games647
+ fastlogin
+ 1.11-SNAPSHOT
+ ../pom.xml
+
+
+
+ fastlogin.velocity
+ jar
+
+
+ FastLoginVelocity
+
+
+
+
+ org.codehaus.mojo
+ templating-maven-plugin
+ 1.0.0
+
+
+ filter-src
+
+ filter-sources
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+ false
+ false
+
+
+ com.zaxxer.hikari
+ fastlogin.hikari
+
+
+ net.md_5.bungee.config
+ fastlogin.config
+
+
+ com.mysql
+ fastlogin.mysql
+
+
+
+
+
+ org.slf4j:*
+ com.google.code.gson:gson
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+ velocity
+ https://nexus.velocitypowered.com/repository/maven-public/
+
+
+
+
+
+
+ ${project.groupId}
+ fastlogin.core
+ ${project.version}
+
+
+
+
+ com.velocitypowered
+ velocity-api
+ 3.0.1
+ provided
+
+
+
+
+ org.mariadb.jdbc
+ mariadb-java-client
+ 2.7.4
+
+
+
diff --git a/velocity/src/main/java-templates/com/github/games647/fastlogin/velocity/PomData.java b/velocity/src/main/java-templates/com/github/games647/fastlogin/velocity/PomData.java
new file mode 100644
index 00000000..340e0ea7
--- /dev/null
+++ b/velocity/src/main/java-templates/com/github/games647/fastlogin/velocity/PomData.java
@@ -0,0 +1,9 @@
+package com.github.games647.fastlogin.velocity;
+
+public class PomData {
+ public static final String DISPLAY_NAME = "${project.name}";
+ public static final String NAME = "${project.parent.artifactId}";
+ public static final String VERSION = "${project.version}-${git.commit.id.abbrev}";
+ public static final String DESCRIPTION = "${project.parent.description}";
+ public static final String URL = "${project.parent.url}";
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java
new file mode 100644
index 00000000..9de54b5a
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java
@@ -0,0 +1,199 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity;
+
+import com.github.games647.fastlogin.core.AsyncScheduler;
+import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
+import com.github.games647.fastlogin.core.message.ChannelMessage;
+import com.github.games647.fastlogin.core.message.SuccessMessage;
+import com.github.games647.fastlogin.core.shared.FastLoginCore;
+import com.github.games647.fastlogin.core.shared.PlatformPlugin;
+import com.github.games647.fastlogin.velocity.listener.ConnectListener;
+import com.github.games647.fastlogin.velocity.listener.PluginMessageListener;
+import com.google.common.collect.MapMaker;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import com.google.inject.Inject;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
+import com.velocitypowered.api.plugin.Plugin;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ProxyServer;
+import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
+import com.velocitypowered.api.proxy.server.RegisteredServer;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentMap;
+
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+import org.slf4j.Logger;
+
+//TODO: Support for floodgate
+@Plugin(id = PomData.NAME, name = PomData.DISPLAY_NAME, description = PomData.DESCRIPTION, url = PomData.URL,
+ version = PomData.VERSION, authors = {"games647", "https://github.com/games647/FastLogin/graphs/contributors"})
+public class FastLoginVelocity implements PlatformPlugin {
+
+ private final ProxyServer server;
+ private final Path dataDirectory;
+ private final Logger logger;
+ private final ConcurrentMap session = new MapMaker().weakKeys().makeMap();
+ private static final String PROXY_ID_fILE = "proxyId.txt";
+
+ private FastLoginCore core;
+ private AsyncScheduler scheduler;
+ private UUID proxyId;
+
+ @Inject
+ public FastLoginVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
+ this.server = server;
+ this.logger = logger;
+ this.dataDirectory = dataDirectory;
+ }
+
+ @Subscribe
+ public void onProxyInitialization(ProxyInitializeEvent event) {
+ scheduler = new AsyncScheduler(logger, getThreadFactory());
+ core = new FastLoginCore<>(this);
+ core.load();
+ loadOrGenerateProxyId();
+ if (!core.setupDatabase() || proxyId == null) {
+ return;
+ }
+
+ server.getEventManager().register(this, new ConnectListener(this, core.getRateLimiter()));
+ server.getEventManager().register(this, new PluginMessageListener(this));
+ server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
+ server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL));
+ }
+
+ @Subscribe
+ public void onProxyShutdown(ProxyShutdownEvent event) {
+ if (core != null) {
+ core.close();
+ }
+ }
+
+ @Override
+ public String getName() {
+ return PomData.NAME;
+ }
+
+ @Override
+ public Path getPluginFolder() {
+ return dataDirectory;
+ }
+
+ @Override
+ public Logger getLog() {
+ return logger;
+ }
+
+ @Override
+ public void sendMessage(CommandSource receiver, String message) {
+ receiver.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
+ }
+
+ @Override
+ public AsyncScheduler getScheduler() {
+ return scheduler;
+ }
+
+ @Override
+ public boolean isPluginInstalled(String name) {
+ return server.getPluginManager().isLoaded(name);
+ }
+
+ public FastLoginCore getCore() {
+ return core;
+ }
+
+ public ConcurrentMap getSession() {
+ return session;
+ }
+
+ public ProxyServer getProxy() {
+ return server;
+ }
+
+ public void sendPluginMessage(RegisteredServer server, ChannelMessage message) {
+ if (server != null) {
+ ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
+ message.writeTo(dataOutput);
+
+ MinecraftChannelIdentifier channel = MinecraftChannelIdentifier.create(getName(), message.getChannelName());
+ server.sendPluginMessage(channel, dataOutput.toByteArray());
+ }
+ }
+
+ private void loadOrGenerateProxyId() {
+ Path idFile = dataDirectory.resolve(PROXY_ID_fILE);
+ boolean shouldGenerate = false;
+
+ if (Files.exists(idFile)) {
+ try {
+ List lines = Files.readAllLines(idFile, StandardCharsets.UTF_8);
+ if (lines.isEmpty()) {
+ shouldGenerate = true;
+ } else {
+ proxyId = UUID.fromString(lines.get(0));
+ }
+ } catch (IOException e) {
+ logger.error("Unable to load proxy id from '{}'", idFile.toAbsolutePath());
+ logger.error("Detailed exception:", e);
+ } catch (IllegalArgumentException e) {
+ logger.error("'{}' contains an invalid uuid! FastLogin will not work without a valid id.", idFile.toAbsolutePath());
+ }
+ } else {
+ shouldGenerate = true;
+ }
+
+ if (shouldGenerate) {
+ proxyId = UUID.randomUUID();
+ try {
+ Files.write(idFile, Collections.singletonList(proxyId.toString()), StandardOpenOption.CREATE);
+ } catch (IOException e) {
+ logger.error("Unable to save proxy id to '{}'", idFile.toAbsolutePath());
+ logger.error("Detailed exception:", e);
+ }
+ }
+ }
+
+ public UUID getProxyId() {
+ return proxyId;
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java
new file mode 100644
index 00000000..be5d314d
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.LoginSession;
+
+public class VelocityLoginSession extends LoginSession {
+ private boolean alreadySaved;
+ private boolean alreadyLogged;
+
+ public VelocityLoginSession(String requestUsername, boolean registered, StoredProfile profile) {
+ super(requestUsername, registered, profile);
+ }
+
+ public synchronized void setRegistered(boolean registered) {
+ this.registered = registered;
+ }
+
+ public synchronized boolean isAlreadySaved() {
+ return alreadySaved;
+ }
+
+ public synchronized void setAlreadySaved(boolean alreadySaved) {
+ this.alreadySaved = alreadySaved;
+ }
+
+ public synchronized boolean isAlreadyLogged() {
+ return alreadyLogged;
+ }
+
+ public synchronized void setAlreadyLogged(boolean alreadyLogged) {
+ this.alreadyLogged = alreadyLogged;
+ }
+
+ @Override
+ public synchronized String toString() {
+ return this.getClass().getSimpleName() + '{' +
+ "alreadySaved=" + alreadySaved +
+ ", alreadyLogged=" + alreadyLogged +
+ ", registered=" + registered +
+ "} " + super.toString();
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java
new file mode 100644
index 00000000..d2ee5102
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java
@@ -0,0 +1,72 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity;
+
+import com.github.games647.fastlogin.core.shared.LoginSource;
+import com.velocitypowered.api.event.connection.PreLoginEvent;
+import com.velocitypowered.api.proxy.InboundConnection;
+
+import java.net.InetSocketAddress;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+public class VelocityLoginSource implements LoginSource {
+
+ private final InboundConnection connection;
+ private final PreLoginEvent preLoginEvent;
+
+ public VelocityLoginSource(InboundConnection connection, PreLoginEvent preLoginEvent) {
+ this.connection = connection;
+ this.preLoginEvent = preLoginEvent;
+ }
+
+ @Override
+ public void enableOnlinemode() {
+ preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.forceOnlineMode());
+ }
+
+ @Override
+ public void kick(String message) {
+ if (message == null) {
+ preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
+ Component.text("Kicked").color(NamedTextColor.WHITE)));
+ } else {
+ preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
+ LegacyComponentSerializer.legacyAmpersand().deserialize(message)));
+ }
+ }
+
+ @Override
+ public InetSocketAddress getAddress() {
+ return connection.getRemoteAddress();
+ }
+
+ public InboundConnection getConnection() {
+ return connection;
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java
new file mode 100644
index 00000000..61ecb1a8
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.event;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.LoginSession;
+import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
+import com.velocitypowered.api.event.ResultedEvent;
+
+import java.util.Objects;
+
+public class VelocityFastLoginAutoLoginEvent
+ implements FastLoginAutoLoginEvent, ResultedEvent {
+
+ private final LoginSession session;
+ private final StoredProfile profile;
+ private boolean cancelled;
+
+ public VelocityFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
+ this.session = session;
+ this.profile = profile;
+ }
+
+ @Override
+ public LoginSession getSession() {
+ return session;
+ }
+
+ @Override
+ public StoredProfile getProfile() {
+ return profile;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+
+ @Override
+ public GenericResult getResult() {
+ return cancelled ? GenericResult.denied(): GenericResult.allowed();
+ }
+
+ @Override
+ public void setResult(GenericResult result) {
+ cancelled = Objects.requireNonNull(result) != GenericResult.allowed();
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java
new file mode 100644
index 00000000..ce58ee52
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.event;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.LoginSource;
+import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
+
+public class VelocityFastLoginPreLoginEvent implements FastLoginPreLoginEvent {
+
+ private final String username;
+ private final LoginSource source;
+ private final StoredProfile profile;
+
+ public VelocityFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
+ this.username = username;
+ this.source = source;
+ this.profile = profile;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public LoginSource getSource() {
+ return source;
+ }
+
+ @Override
+ public StoredProfile getProfile() {
+ return profile;
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java
new file mode 100644
index 00000000..3dc83b1a
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.event;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
+
+public class VelocityFastLoginPremiumToggleEvent implements FastLoginPremiumToggleEvent {
+
+ private final StoredProfile profile;
+ private final PremiumToggleReason reason;
+
+ public VelocityFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
+ this.profile = profile;
+ this.reason = reason;
+ }
+
+ @Override
+ public StoredProfile getProfile() {
+ return profile;
+ }
+
+ @Override
+ public PremiumToggleReason getReason() {
+ return reason;
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java
new file mode 100644
index 00000000..acb24c7b
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java
@@ -0,0 +1,141 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.listener;
+
+import com.github.games647.craftapi.UUIDAdapter;
+import com.github.games647.fastlogin.core.RateLimiter;
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.LoginSession;
+import com.github.games647.fastlogin.velocity.FastLoginVelocity;
+import com.github.games647.fastlogin.velocity.VelocityLoginSession;
+import com.github.games647.fastlogin.velocity.task.AsyncPremiumCheck;
+import com.github.games647.fastlogin.velocity.task.ForceLoginTask;
+import com.velocitypowered.api.event.Continuation;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.connection.DisconnectEvent;
+import com.velocitypowered.api.event.connection.PreLoginEvent;
+import com.velocitypowered.api.event.player.GameProfileRequestEvent;
+import com.velocitypowered.api.event.player.ServerConnectedEvent;
+import com.velocitypowered.api.proxy.InboundConnection;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.server.RegisteredServer;
+import com.velocitypowered.api.util.GameProfile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+public class ConnectListener {
+
+ private final FastLoginVelocity plugin;
+ private final RateLimiter rateLimiter;
+
+ public ConnectListener(FastLoginVelocity plugin, RateLimiter rateLimiter) {
+ this.plugin = plugin;
+ this.rateLimiter = rateLimiter;
+ }
+
+ @Subscribe
+ public void onPreLogin(PreLoginEvent preLoginEvent, Continuation continuation) {
+ if (!preLoginEvent.getResult().isAllowed()) {
+ return;
+ }
+
+ InboundConnection connection = preLoginEvent.getConnection();
+ if (!rateLimiter.tryAcquire()) {
+ plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
+ return;
+ }
+
+ String username = preLoginEvent.getUsername();
+ plugin.getLog().info("Incoming login request for {} from {}", username, connection.getRemoteAddress());
+
+ Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, connection, username, continuation, preLoginEvent);
+ plugin.getScheduler().runAsync(asyncPremiumCheck);
+ }
+
+ @Subscribe
+ public void onGameprofileRequest(GameProfileRequestEvent event) {
+ if (event.isOnlineMode()) {
+ LoginSession session = plugin.getSession().get(event.getConnection().getRemoteAddress());
+
+ UUID verifiedUUID = event.getGameProfile().getId();
+ String verifiedUsername = event.getUsername();
+ session.setUuid(verifiedUUID);
+ session.setVerifiedUsername(verifiedUsername);
+
+ StoredProfile playerProfile = session.getProfile();
+ playerProfile.setId(verifiedUUID);
+ if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
+ UUID offlineUUID = UUIDAdapter.generateOfflineId(playerProfile.getName());
+ event.setGameProfile(event.getGameProfile().withId(offlineUUID));
+ plugin.getLog().info("Overridden UUID from {} to {} (based of {}) on {}",
+ verifiedUUID, offlineUUID, verifiedUsername, event.getConnection());
+ }
+
+ if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
+ event.setGameProfile(event.getGameProfile().withProperties(removeSkin(event.getGameProfile().getProperties())));
+ }
+ }
+ }
+
+ private List removeSkin(List oldProperties) {
+ List newProperties = new ArrayList<>(oldProperties.size() - 1);
+ for (GameProfile.Property property : oldProperties) {
+ if (!"textures".equals(property.getName()))
+ newProperties.add(property);
+ }
+
+ return newProperties;
+ }
+
+ @Subscribe
+ public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
+ Player player = serverConnectedEvent.getPlayer();
+ RegisteredServer server = serverConnectedEvent.getServer();
+
+ VelocityLoginSession session = plugin.getSession().get(player.getRemoteAddress());
+ if (session == null) {
+ return;
+ }
+
+ // delay sending force command, because Paper will process the login event asynchronously
+ // In this case it means that the force command (plugin message) is already received and processed while
+ // player is still in the login phase and reported to be offline.
+ Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
+ plugin.getProxy().getScheduler()
+ .buildTask(plugin, () -> plugin.getScheduler().runAsync(loginTask))
+ .delay(1L, TimeUnit.SECONDS) // Delay at least one second, otherwise the login command can be missed
+ .schedule();
+ }
+
+ @Subscribe
+ public void onDisconnect(DisconnectEvent disconnectEvent) {
+ Player player = disconnectEvent.getPlayer();
+ plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java
new file mode 100644
index 00000000..b9df0a8a
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java
@@ -0,0 +1,129 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.listener;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
+import com.github.games647.fastlogin.core.message.SuccessMessage;
+import com.github.games647.fastlogin.core.shared.FastLoginCore;
+import com.github.games647.fastlogin.velocity.FastLoginVelocity;
+import com.github.games647.fastlogin.velocity.VelocityLoginSession;
+import com.github.games647.fastlogin.velocity.task.AsyncToggleMessage;
+import com.google.common.io.ByteArrayDataInput;
+import com.google.common.io.ByteStreams;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.connection.PluginMessageEvent;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ServerConnection;
+import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+import java.util.Arrays;
+
+public class PluginMessageListener {
+
+ private final FastLoginVelocity plugin;
+
+ private final String successChannel;
+ private final String changeChannel;
+
+ public PluginMessageListener(FastLoginVelocity plugin) {
+ this.plugin = plugin;
+
+ this.successChannel = MinecraftChannelIdentifier.create(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getId();
+ this.changeChannel = MinecraftChannelIdentifier.create(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getId();
+ }
+
+ @Subscribe
+ public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
+ String channel = pluginMessageEvent.getIdentifier().getId();
+ if (!pluginMessageEvent.getResult().isAllowed() || !channel.startsWith(plugin.getName().toLowerCase())) {
+ return;
+ }
+
+ //the client shouldn't be able to read the messages in order to know something about server internal states
+ //moreover the client shouldn't be able fake a running premium check by sending the result message
+ pluginMessageEvent.setResult(PluginMessageEvent.ForwardResult.handled());
+
+ if (!(pluginMessageEvent.getSource() instanceof ServerConnection)) {
+ //check if the message is sent from the server
+ return;
+ }
+
+ //so that we can safely process this in the background
+ byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
+ Player forPlayer = (Player) pluginMessageEvent.getTarget();
+
+ plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
+ }
+
+ private void readMessage(Player forPlayer, String channel, byte[] data) {
+ FastLoginCore core = plugin.getCore();
+
+ ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
+ if (successChannel.equals(channel)) {
+ onSuccessMessage(forPlayer);
+ } else if (changeChannel.equals(channel)) {
+ ChangePremiumMessage changeMessage = new ChangePremiumMessage();
+ changeMessage.readFrom(dataInput);
+
+ String playerName = changeMessage.getPlayerName();
+ boolean isSourceInvoker = changeMessage.isSourceInvoker();
+ if (changeMessage.shouldEnable()) {
+ if (playerName.equals(forPlayer.getUsername()) && plugin.getCore().getConfig().get("premium-warning", true)
+ && !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
+ String message = core.getMessage("premium-warning");
+ forPlayer.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
+ core.getPendingConfirms().add(forPlayer.getUniqueId());
+ return;
+ }
+
+ core.getPendingConfirms().remove(forPlayer.getUniqueId());
+ Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
+ plugin.getScheduler().runAsync(task);
+ } else {
+ Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
+ plugin.getScheduler().runAsync(task);
+ }
+ }
+ }
+
+ private void onSuccessMessage(Player forPlayer) {
+ if (forPlayer.isOnlineMode()){
+ //bukkit module successfully received and force logged in the user
+ //update only on success to prevent corrupt data
+ VelocityLoginSession loginSession = plugin.getSession().get(forPlayer.getRemoteAddress());
+ StoredProfile playerProfile = loginSession.getProfile();
+ loginSession.setRegistered(true);
+ if (!loginSession.isAlreadySaved()) {
+ playerProfile.setPremium(true);
+ plugin.getCore().getStorage().save(playerProfile);
+ loginSession.setAlreadySaved(true);
+ }
+ }
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java
new file mode 100644
index 00000000..cb342da4
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.task;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.JoinManagement;
+import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
+import com.github.games647.fastlogin.velocity.FastLoginVelocity;
+import com.github.games647.fastlogin.velocity.VelocityLoginSession;
+import com.github.games647.fastlogin.velocity.VelocityLoginSource;
+import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPreLoginEvent;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.event.Continuation;
+import com.velocitypowered.api.event.connection.PreLoginEvent;
+import com.velocitypowered.api.proxy.InboundConnection;
+import com.velocitypowered.api.proxy.Player;
+
+import java.util.concurrent.ExecutionException;
+
+public class AsyncPremiumCheck extends JoinManagement
+ implements Runnable {
+
+ private final FastLoginVelocity plugin;
+ private final String username;
+ private Continuation continuation;
+ private PreLoginEvent preLoginEvent;
+ private final InboundConnection connection;
+
+ public AsyncPremiumCheck(FastLoginVelocity plugin, InboundConnection connection, String username, Continuation continuation, PreLoginEvent preLoginEvent) {
+ super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
+ this.plugin = plugin;
+ this.connection = connection;
+ this.username = username;
+ this.continuation = continuation;
+ this.preLoginEvent = preLoginEvent;
+ }
+
+ @Override
+ public void run() {
+ plugin.getSession().remove(connection.getRemoteAddress());
+ try {
+ super.onLogin(username, new VelocityLoginSource(connection, preLoginEvent));
+ } finally {
+ continuation.resume();
+ }
+ }
+
+ @Override
+ public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, VelocityLoginSource source, StoredProfile profile) {
+ VelocityFastLoginPreLoginEvent event = new VelocityFastLoginPreLoginEvent(username, source, profile);
+ try {
+ return plugin.getProxy().getEventManager().fire(event).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // Restore the interrupt flag
+ return event;
+ } catch (ExecutionException e) {
+ core.getPlugin().getLog().error("Error firing event", e);
+ return event;
+ }
+ }
+
+ @Override
+ public void requestPremiumLogin(VelocityLoginSource source, StoredProfile profile,
+ String username, boolean registered) {
+ source.enableOnlinemode();
+ plugin.getSession().put(source.getConnection().getRemoteAddress(), new VelocityLoginSession(username, registered, profile));
+
+ String ip = source.getAddress().getAddress().getHostAddress();
+ plugin.getCore().getPendingLogin().put(ip + username, new Object());
+ }
+
+ @Override
+ public void startCrackedSession(VelocityLoginSource source, StoredProfile profile, String username) {
+ plugin.getSession().put(source.getConnection().getRemoteAddress(), new VelocityLoginSession(username, false, profile));
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java
new file mode 100644
index 00000000..d1b24cf5
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java
@@ -0,0 +1,110 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.task;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.shared.FastLoginCore;
+import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
+import com.github.games647.fastlogin.velocity.FastLoginVelocity;
+import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPremiumToggleEvent;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.proxy.Player;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+public class AsyncToggleMessage implements Runnable {
+
+ private final FastLoginCore core;
+ private final CommandSource sender;
+ private final String senderName;
+ private final String targetPlayer;
+ private final boolean toPremium;
+ private final boolean isPlayerSender;
+
+ public AsyncToggleMessage(FastLoginCore core,
+ CommandSource sender, String playerName, boolean toPremium, boolean playerSender) {
+ this.core = core;
+ this.sender = sender;
+ this.targetPlayer = playerName;
+ this.toPremium = toPremium;
+ this.isPlayerSender = playerSender;
+ if (sender instanceof Player)
+ senderName = ((Player) sender).getUsername();
+ else
+ senderName = "";
+ }
+
+ @Override
+ public void run() {
+ if (toPremium) {
+ activatePremium();
+ } else {
+ turnOffPremium();
+ }
+ }
+
+ private void turnOffPremium() {
+ StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
+ //existing player is already cracked
+ if (playerProfile.isSaved() && !playerProfile.isPremium()) {
+ sendMessage("not-premium");
+ return;
+ }
+
+ playerProfile.setPremium(false);
+ playerProfile.setId(null);
+ core.getStorage().save(playerProfile);
+ PremiumToggleReason reason = (!isPlayerSender || !senderName.equalsIgnoreCase(playerProfile.getName())) ?
+ PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
+ core.getPlugin().getProxy().getEventManager().fire(
+ new VelocityFastLoginPremiumToggleEvent(playerProfile, reason));
+ sendMessage("remove-premium");
+ }
+
+ private void activatePremium() {
+ StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
+ if (playerProfile.isPremium()) {
+ sendMessage("already-exists");
+ return;
+ }
+
+ playerProfile.setPremium(true);
+ core.getStorage().save(playerProfile);
+ PremiumToggleReason reason = (!isPlayerSender || !senderName.equalsIgnoreCase(playerProfile.getName())) ?
+ PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
+ core.getPlugin().getProxy().getEventManager().fire(
+ new VelocityFastLoginPremiumToggleEvent(playerProfile, reason));
+ sendMessage("add-premium");
+ }
+
+ private void sendMessage(String localeId) {
+ String message = core.getMessage(localeId);
+ if (isPlayerSender) {
+ sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
+ } else {
+ core.getPlugin().getProxy().getConsoleCommandSource().sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
+ }
+ }
+}
diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java
new file mode 100644
index 00000000..c648b24d
--- /dev/null
+++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java
@@ -0,0 +1,136 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2021
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.github.games647.fastlogin.velocity.task;
+
+import com.github.games647.fastlogin.core.StoredProfile;
+import com.github.games647.fastlogin.core.message.ChannelMessage;
+import com.github.games647.fastlogin.core.message.LoginActionMessage;
+import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
+import com.github.games647.fastlogin.core.shared.FastLoginCore;
+import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
+import com.github.games647.fastlogin.core.shared.LoginSession;
+import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
+import com.github.games647.fastlogin.velocity.FastLoginVelocity;
+import com.github.games647.fastlogin.velocity.VelocityLoginSession;
+import com.github.games647.fastlogin.velocity.event.VelocityFastLoginAutoLoginEvent;
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.server.RegisteredServer;
+
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+
+public class ForceLoginTask
+ extends ForceLoginManagement {
+
+ private final RegisteredServer server;
+
+ //treat player as if they had a premium account, even when they don't
+ //used for Floodgate auto login/register
+ private final boolean forcedOnlineMode;
+
+ public ForceLoginTask(FastLoginCore core,
+ Player player, RegisteredServer server, VelocityLoginSession session, boolean forcedOnlineMode) {
+ super(core, player, session);
+
+ this.server = server;
+ this.forcedOnlineMode = forcedOnlineMode;
+ }
+
+ public ForceLoginTask(FastLoginCore core, Player player,
+ RegisteredServer server, VelocityLoginSession session) {
+ this(core, player, server, session, false);
+ }
+
+ @Override
+ public void run() {
+ if (session == null) {
+ return;
+ }
+
+ super.run();
+ if (!isOnlineMode()) {
+ session.setAlreadySaved(true);
+ }
+ }
+
+ @Override
+ public boolean forceLogin(Player player) {
+ if (session.isAlreadyLogged()) {
+ return true;
+ }
+
+ session.setAlreadyLogged(true);
+ return super.forceLogin(player);
+ }
+
+ @Override
+ public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
+ VelocityFastLoginAutoLoginEvent event = new VelocityFastLoginAutoLoginEvent(session, profile);
+ try {
+ return core.getPlugin().getProxy().getEventManager().fire(event).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // Set the interrupt flag again
+ return event;
+ } catch (ExecutionException e) {
+ core.getPlugin().getLog().error("Error firing event", e);
+ return event;
+ }
+ }
+
+ @Override
+ public boolean forceRegister(Player player) {
+ return session.isAlreadyLogged() || super.forceRegister(player);
+ }
+
+ @Override
+ public void onForceActionSuccess(LoginSession session) {
+ //sub channel name
+ Type type = Type.LOGIN;
+ if (session.needsRegistration()) {
+ type = Type.REGISTER;
+ }
+
+ UUID proxyId = core.getPlugin().getProxyId();
+ ChannelMessage loginMessage = new LoginActionMessage(type, player.getUsername(), proxyId);
+ core.getPlugin().sendPluginMessage(server, loginMessage);
+ }
+
+ @Override
+ public String getName(Player player) {
+ return player.getUsername();
+ }
+
+ @Override
+ public boolean isOnline(Player player) {
+ return player.isActive();
+ }
+
+ @Override
+ public boolean isOnlineMode() {
+ return forcedOnlineMode || player.isOnlineMode();
+ }
+}