1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Gradle 8, platform isolation, bundled libraries, work on local linking

This commit is contained in:
Tim203
2023-04-24 00:18:03 +02:00
parent ef7251b933
commit 184389f11f
61 changed files with 753 additions and 1119 deletions

View File

@@ -1,9 +0,0 @@
plugins {
id("floodgate.shadow-conventions")
}
tasks {
shadowJar {
archiveBaseName.set(archiveBaseName.get() + "-database")
}
}

View File

@@ -17,10 +17,9 @@ tasks.register("listDependencyInfo") {
tasks.register("writeDependencyInfo") {
doLast {
Files.writeString(
dependenciesInfoFile(),
dependenciesToString(listDependencies())
)
val path = dependenciesInfoFile()
Files.createDirectories(path.parent)
Files.writeString(path, dependenciesToString(listDependencies()))
}
}
@@ -34,9 +33,9 @@ tasks.register("checkDependencyInfoFile") {
}
}
tasks.named("build") {
dependsOn("checkDependencyHashesFile")
}
//tasks.named("build") {
// dependsOn("checkDependencyInfoFile")
//}
fun dependenciesToString(dependencies: Collection<Dependency>): String {
val builder = StringBuilder()

View File

@@ -10,6 +10,7 @@ tasks {
archiveClassifier.set("unshaded")
from(project.rootProject.file("LICENSE"))
}
val shadowJar = named<ShadowJar>("shadowJar") {
archiveBaseName.set("floodgate-${project.name}")
archiveVersion.set("")
@@ -39,6 +40,7 @@ tasks {
destinationDirectory.set(file(destinationDir))
}
}
named("build") {
dependsOn(shadowJar)
}

View File

@@ -1,7 +1,8 @@
plugins {
`java-library`
id("floodgate.build-logic")
id("io.freefair.lombok") version "6.3.0" apply false
id("io.freefair.lombok") version "8.0.1" apply false
id("io.micronaut.library") version "3.7.8" apply false
}
allprojects {
@@ -16,7 +17,7 @@ val deployProjects = setOf(
projects.core,
projects.bungee,
projects.spigot,
projects.velocity,
projects.velocityIsolated,
projects.universal
).map { it.dependencyProject }
@@ -30,13 +31,6 @@ subprojects {
plugin("floodgate.build-logic")
}
val relativePath = projectDir.relativeTo(rootProject.projectDir).path
if (relativePath.startsWith("database" + File.separator)) {
group = rootProject.group as String + ".database"
plugins.apply("floodgate.database-conventions")
}
when (this) {
in deployProjects -> plugins.apply("floodgate.publish-conventions")
else -> plugins.apply("floodgate.base-conventions")

View File

@@ -1,11 +1,12 @@
plugins {
id("floodgate.generate-templates")
id("floodgate.dependency-hash")
id("io.micronaut.library") version "3.7.4"
id("io.micronaut.library")
}
dependencies {
api(projects.api)
compileOnlyApi(projects.isolation)
api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion)
api("com.google.inject", "guice", Versions.guiceVersion)
@@ -23,9 +24,17 @@ dependencies {
api("io.micronaut", "micronaut-context")
annotationProcessor("io.micronaut.data:micronaut-data-processor")
implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("com.h2database:h2")
implementation("io.micronaut.data:micronaut-data-model")
implementation("jakarta.persistence:jakarta.persistence-api:2.2.3")
// compileOnlyApi("io.micronaut.data:micronaut-data-hibernate-jpa")
//todo add these as libs
//compileOnly("io.micronaut.data:micronaut-data-hibernate-jpa")
//compileOnly("io.micronaut.sql:micronaut-jdbc-hikari")
//compileOnly("com.h2database:h2")
//implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
//implementation("io.micronaut.sql:micronaut-jdbc-hikari")
//runtimeOnly("com.h2database:h2")
// annotationProcessor("io.micronaut.data:micronaut-data-document-processor")
// compileOnly("io.micronaut.data:micronaut-data-mongodb")

View File

@@ -26,7 +26,6 @@
package org.geysermc.floodgate.core;
import io.micronaut.context.ApplicationContext;
import java.nio.file.Paths;
import java.util.Map;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
@@ -43,18 +42,23 @@ import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
import org.geysermc.floodgate.core.event.EventBus;
import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent;
import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.core.library.Library;
import org.geysermc.floodgate.core.library.LibraryManager;
import org.geysermc.floodgate.core.library.Repository;
import org.geysermc.floodgate.core.library.info.DependencyInfoLoader;
import org.geysermc.floodgate.core.util.EagerSingleton;
import org.geysermc.floodgate.isolation.library.Library;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.library.Repository;
import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader;
public abstract class FloodgatePlatform {
private static final UUID KEY = UUID.randomUUID();
private final LibraryManager manager;
private ApplicationContext context;
private PlatformInjector injector;
protected FloodgatePlatform(LibraryManager manager) {
this.manager = manager;
}
protected void onContextCreated(ApplicationContext context) {
}
@@ -65,19 +69,28 @@ public abstract class FloodgatePlatform {
getClass().getClassLoader().getResource("dependencyInfo.txt")
);
new LibraryManager(ClassLoader.getSystemClassLoader().getParent(), Paths.get("./libs"))
manager
// .addLibrary(
// Library.builder(infoLoader)
// .id("guava")
// .repository(Repository.MAVEN_CENTRAL)
// .groupId("com.google.guava")
// .artifactId("guava")
// .build()
// )
.addLibrary(
Library.builder(infoLoader)
.id("guava")
.repository(Repository.MAVEN_CENTRAL)
.groupId("com.google.guava")
.artifactId("guava")
Library.builder()
.id("local-linking")
.repository(Repository.OPEN_COLLAB)
.groupId("org.geysermc.floodgate")
.artifactId("database")
.version("a-version")
.build()
)
.apply();
//noinspection unchecked
context = ApplicationContext.builder()
context = ApplicationContext.builder(manager.classLoader())
.properties(Map.of(
"platform.proxy", isProxy()
))
@@ -140,8 +153,6 @@ public abstract class FloodgatePlatform {
throw new RuntimeException("Failed to inject the packet listener!", exception);
}
// this.guice = guice.createChildInjector(new PostEnableModules(postEnableStageModules()));
context.getBean(EventBus.class).fire(new PostEnableEvent());
}

View File

@@ -30,12 +30,9 @@ import com.google.gson.JsonObject;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -97,26 +94,6 @@ public class HttpClient {
return connection;
}
@NonNull
public HttpResponse<byte[]> getRawData(String urlString) throws IOException {
HttpURLConnection connection = request(urlString);
try (InputStream inputStream = connection.getInputStream()) {
int responseCode = connection.getResponseCode();
byte[] buffer = new byte[8196];
int len;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer, 0, len);
}
return new HttpResponse<>(responseCode, outputStream.toByteArray());
}
} catch (SocketTimeoutException | NullPointerException exception) {
return new HttpResponse<>(-1, null);
}
}
@NonNull
private <T> HttpResponse<T> readResponse(HttpURLConnection connection, Class<T> clazz) {
InputStreamReader streamReader = createReader(connection);

View File

@@ -34,7 +34,6 @@ import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Locale;
import java.util.Properties;
import java.util.UUID;
@@ -134,8 +133,4 @@ public class Utils {
future.completeExceptionally(ex);
return future;
}
public static String base64Encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
}

View File

@@ -1,9 +1,10 @@
org.geysermc.configutils:configutils:1.0-SNAPSHOT:Xyh6oCOT1RwP5hw1gRM77M0mOJ86nNPf4J9Hs9PL4Ek=
org.geysermc.configutils:configutils:2.0-SNAPSHOT:HiYf2qgXat5WOQOScSXPk8FBIKZDWYowCJPVJ+Uuv+0=
com.google.inject:guice:5.1.0:QTDlC/rEgJnIYPDZA7kYYMgaJJyQ84JF+P7Vj8gXvCY=
com.nukkitx.fastutil:fastutil-short-object-maps:8.5.3:nTF5o2PCLMeBm8gaPeU4Q41FG2CNpmC9PxeJVHZot+U=
com.nukkitx.fastutil:fastutil-int-object-maps:8.5.3:Z0iKxf9OmoIcQtmqV9Lo9Z39XSpBXKjL8tavAdVCKBY=
org.java-websocket:Java-WebSocket:1.5.2:/4adGYqNxdAJZzkuGHrtqTuDO3zdO8zvs9f7WDQJi4U=
cloud.commandframework:cloud-core:1.5.0:+hDJlLwU84P6sf0gkDq+xStIqAHhMXUCI+Wb8VsD1Zk=
io.micronaut.data:micronaut-data-model:3.9.6:Hdt2G2LRuAtIs1K2DayRz7OoiqZcbIfCUEstVIpQnUE=
io.micronaut.sql:micronaut-jdbc-hikari:4.7.2:w2N1dH4vY/QjjQGsViMOwgVYxAnZNGgMm1doCa19wOA=
io.micronaut.data:micronaut-data-hibernate-jpa:3.9.6:kFYugtQeXfLjqUCWrf3MFFDoUdYhXrPQmSh0pu2Zxzo=
io.micronaut:micronaut-context:3.8.7:ir020Tq3UavnPZhXOUNHCl/XdGcVrOzhM1lU5NT6YEc=

20
database/build.gradle.kts Normal file
View File

@@ -0,0 +1,20 @@
plugins {
id("floodgate.shadow-conventions")
id("io.micronaut.library")
id("floodgate.dependency-hash")
}
configurations.runtimeClasspath.get()
.exclude("org.slf4j", "slf4j-api")
.exclude("javax.validation", "validation-api")
.exclude("io.micronaut", "micronaut-aop")
.exclude("io.micronaut", "micronaut-core")
.exclude("io.micronaut", "micronaut-runtime")
.exclude("io.micronaut", "micronaut-inject")
.exclude("io.micronaut", "micronaut-context")
dependencies {
implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("com.h2database:h2")
}

View File

@@ -1,11 +0,0 @@
val mongoClientVersion = "4.4.1"
dependencies {
provided(projects.core)
implementation("org.mongodb", "mongodb-driver-sync" , mongoClientVersion)
}
description = "The Floodgate database extension for MongoDB"
relocate("com.mongodb")
relocate("org.bson")

View File

@@ -1,343 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.UpdateOptions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.internal.Base64;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.database.config.MongoConfig;
import org.geysermc.floodgate.util.LinkedPlayer;
public class MongoDbDatabase extends CommonPlayerLink {
private MongoClient client;
private MongoDatabase database;
private MongoCollection<Document> linkedPlayer;
private MongoCollection<Document> linkedPlayerRequests;
@Override
public void load() {
getLogger().info("Connecting to MongoDB database...");
try {
MongoConfig databaseConfig = getConfig(MongoConfig.class);
MongoClientSettings.Builder settings = MongoClientSettings.builder();
settings.applyToConnectionPoolSettings(builder -> {
builder.maxSize(10);
builder.minSize(2);
});
if (databaseConfig.getMongouri().isEmpty()) {
settings.credential(
MongoCredential.createCredential(
databaseConfig.getUsername(),
databaseConfig.getDatabase(),
databaseConfig.getPassword().toCharArray()
)
);
} else {
settings.applyConnectionString(new ConnectionString(databaseConfig.getMongouri()));
}
client = MongoClients.create(settings.build());
database = client.getDatabase(databaseConfig.getDatabase());
linkedPlayer = database.getCollection("LinkedPlayers");
if (collectionNotExists("LinkedPlayers")) {
database.createCollection("LinkedPlayers");
linkedPlayer.createIndex(new Document("bedrockId", 1),
new IndexOptions().unique(true)); // primary key equivalent
linkedPlayer.createIndex(Indexes.ascending("javaUniqueId"));
}
linkedPlayerRequests = database.getCollection("LinkedPlayerRequests");
if (collectionNotExists("LinkedPlayerRequests")) {
database.createCollection("LinkedPlayerRequests");
linkedPlayerRequests.createIndex(new Document("bedrockId", 1),
new IndexOptions().unique(true)); // primary key equivalent
linkedPlayerRequests.createIndex(Indexes.ascending("requestTime"));
}
getLogger().info("Connected to MongoDB database.");
} catch (Exception exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
client.close();
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try {
Bson filter = Filters.eq("bedrockId", uuidToBytes(bedrockId));
try (MongoCursor<Document> cursor = linkedPlayer.find(filter).cursor()) {
if (cursor.hasNext()) {
Document document = cursor.next();
String javaUsername = document.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(document.getString("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
return null;
} catch (Exception exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try {
String uuidBytes = uuidToBytes(playerId);
Bson filter = Filters.or(
Filters.eq("bedrockId", uuidBytes),
Filters.eq("javaUniqueId", uuidBytes)
);
try (MongoCursor<Document> cursor = linkedPlayer.find(filter).cursor()) {
return cursor.hasNext();
}
} catch (Exception exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try {
Bson filter = Filters.eq("javaUsername", javaUsername);
Document create = new Document("bedrockId", uuidToBytes(bedrockId))
.append("javaUniqueId", uuidToBytes(javaId))
.append("javaUsername", javaUsername);
Document update = new Document("$set", create);
linkedPlayer.updateOne(filter, update, new UpdateOptions().upsert(true));
// The upsert option will create a new document if the filter doesn't match anything.
// Or will update the document if it does match.
} catch (Exception exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try {
String uuidBytes = uuidToBytes(javaId);
Bson filter = Filters.and(
Filters.eq("javaUniqueId", uuidBytes),
Filters.eq("bedrockId", uuidBytes)
);
linkedPlayer.deleteMany(filter);
} catch (Exception exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername) {
try {
Bson filter = Filters.eq("javaUsername", javaUsername);
Document create = new Document("javaUsername", javaUsername)
.append("javaUniqueId", uuidToBytes(javaId))
.append("linkCode", linkCode)
.append("bedrockUsername", bedrockUsername)
.append("requestTime", Instant.now().getEpochSecond());
Document update = new Document("$set", create);
linkedPlayerRequests.updateOne(filter, update, new UpdateOptions().upsert(true));
// The upsert option will create a new document if the filter doesn't match anything.
// Or will update the document if it does match.
} catch (Exception exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
private void removeLinkRequest(String javaUsername) {
try {
Document filter = new Document("javaUsername", javaUsername);
linkedPlayerRequests.deleteMany(filter);
} catch (Exception exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
private LinkRequest getLinkRequest0(String javaUsername) {
try {
Bson filter = Filters.eq("javaUsername", javaUsername);
try (MongoCursor<Document> cursor = linkedPlayerRequests.find(filter).cursor()) {
if (cursor.hasNext()) {
Document document = cursor.next();
UUID javaId = bytesToUUID(document.getString("javaUniqueId"));
String linkCode = document.getString("linkCode");
String bedrockUsername = document.getString("bedrockUsername");
long requestTime = document.getLong("requestTime");
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
}
} catch (Exception exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
}
return null;
}
public void cleanLinkRequests() {
try {
Document filter = new Document("requestTime",
new Document("$lt", Instant.now().getEpochSecond() - getVerifyLinkTimeout()));
linkedPlayerRequests.deleteMany(filter);
} catch (Exception exception) {
getLogger().error("Error while cleaning up link requests", exception);
}
}
private String uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return Base64.encode(uuidBytes);
}
private UUID bytesToUUID(String uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(Base64.decode(uuidBytes));
return new UUID(buf.getLong(), buf.getLong());
}
public boolean collectionNotExists(final String collectionName) {
return !database.listCollectionNames().into(new ArrayList<>()).contains(collectionName);
}
}

View File

@@ -1,4 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.MongoDbDatabase",
"config": "mongo.yml"
}

View File

@@ -1,5 +0,0 @@
hostname: "localhost"
database: "floodgate"
username: "floodgate"
password: ""
mongouri: ""

View File

@@ -1,4 +0,0 @@
[*]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 4

View File

@@ -1,12 +0,0 @@
val mysqlClientVersion = "8.0.30"
val hikariVersion = "4.0.3"
dependencies {
provided(projects.core)
implementation("mysql", "mysql-connector-java", mysqlClientVersion)
implementation("com.zaxxer", "HikariCP", hikariVersion)
}
description = "The Floodgate database extension for MySQL"
relocate("org.mariadb")

View File

@@ -1,340 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.database.config.MysqlConfig;
import org.geysermc.floodgate.util.LinkedPlayer;
public class MysqlDatabase extends CommonPlayerLink {
private HikariDataSource dataSource;
@Override
public void load() {
getLogger().info("Connecting to a MySQL-like database...");
try {
MysqlConfig config = getConfig(MysqlConfig.class);
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://" + config.getHostname() + "/" + config.getDatabase());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
hikariConfig.setPoolName("floodgate-linking-mysql");
hikariConfig.setMinimumIdle(5);
hikariConfig.setMaximumPoolSize(10);
dataSource = new HikariDataSource(hikariConfig);
try (Connection connection = dataSource.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " +
"`bedrockId` BINARY(16) NOT NULL , " +
"`javaUniqueId` BINARY(16) NOT NULL , " +
"`javaUsername` VARCHAR(16) NOT NULL , " +
" PRIMARY KEY (`bedrockId`) , " +
" INDEX (`bedrockId`, `javaUniqueId`)" +
") ENGINE = InnoDB;"
);
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayersRequest` ( " +
"`javaUsername` VARCHAR(16) NOT NULL , `javaUniqueId` BINARY(16) NOT NULL , " +
"`linkCode` VARCHAR(16) NOT NULL , " +
"`bedrockUsername` VARCHAR(16) NOT NULL ," +
"`requestTime` BIGINT NOT NULL , " +
" PRIMARY KEY (`javaUsername`), INDEX(`requestTime`)" +
" ) ENGINE = InnoDB;"
);
}
}
getLogger().info("Connected to MySQL-like database.");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
dataSource.close();
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
try (ResultSet result = query.executeQuery()) {
if (!result.next()) {
return null;
}
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(result.getBytes("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
} catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(playerId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
try (ResultSet result = query.executeQuery()) {
return result.next();
}
}
} catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`javaUsername`=VALUES(`javaUsername`);"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(javaId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername
) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername
) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`linkCode`=VALUES(`linkCode`), " +
"`bedrockUsername`=VALUES(`bedrockUsername`), " +
"`requestTime`=VALUES(`requestTime`);"
)) {
query.setString(1, javaUsername);
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, linkCode);
query.setString(4, bedrockUsername);
query.setLong(5, Instant.now().getEpochSecond());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
private void removeLinkRequest(String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code
) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
private LinkRequest getLinkRequest0(String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
try (ResultSet result = query.executeQuery()) {
if (result.next()) {
UUID javaId = bytesToUUID(result.getBytes(2));
String linkCode = result.getString(3);
String bedrockUsername = result.getString(4);
long requestTime = result.getLong(5);
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
}
}
} catch (SQLException exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
}
return null;
}
public void cleanLinkRequests() {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?"
)) {
query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up link requests", exception);
}
}
private byte[] uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return uuidBytes;
}
private UUID bytesToUUID(byte[] uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong());
}
}

View File

@@ -1,4 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.MysqlDatabase",
"config": "mysql.yml"
}

View File

@@ -1,4 +0,0 @@
hostname: "localhost"
database: "floodgate"
username: "floodgate"
password: ""

View File

@@ -1,8 +0,0 @@
val sqliteJdbcVersion = "3.36.0.3"
dependencies {
provided(projects.core)
implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion)
}
description = "The Floodgate database extension for SQLite"

View File

@@ -1,217 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import javax.inject.Inject;
import javax.inject.Named;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.util.LinkedPlayer;
public class SqliteDatabase extends CommonPlayerLink {
private final Map<String, LinkRequest> activeLinkRequests = new HashMap<>();
private Connection connection;
/* These are DELIBERATELY javax imports so Guice relocations can't break it */
@Inject
@Named("dataDirectory")
private Path dataDirectory;
@Override
public void load() {
Path databasePath = dataDirectory.resolve("linked-players.db");
try {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath);
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(
"create table if not exists LinkedPlayers (bedrockId string, javaUniqueId string, javaUsername string)"
);
}
} catch (ClassNotFoundException exception) {
getLogger().error("The required class to load the SQLite database wasn't found");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
try {
connection.close();
} catch (SQLException exception) {
getLogger().error("Error while closing database connection", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try (PreparedStatement query = connection.prepareStatement(
"select * from LinkedPlayers where bedrockId = ?")) {
query.setString(1, bedrockId.toString());
try (ResultSet result = query.executeQuery()) {
if (!result.next()) {
return null;
}
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = UUID.fromString(result.getString("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
} catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (PreparedStatement query = connection.prepareStatement(
"select javaUniqueId from LinkedPlayers where bedrockId = ? or javaUniqueId = ?")) {
query.setString(1, playerId.toString());
query.setString(2, playerId.toString());
try (ResultSet result = query.executeQuery()) {
return result.next();
}
} catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String username) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, username),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String username) {
try (PreparedStatement query =
connection.prepareStatement("insert into LinkedPlayers values(?, ?, ?)")) {
query.setString(1, bedrockId.toString());
query.setString(2, javaId.toString());
query.setString(3, username);
query.executeUpdate();
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try (PreparedStatement query = connection.prepareStatement(
"delete from LinkedPlayers where javaUniqueId = ? or bedrockId = ?")) {
query.setString(1, javaId.toString());
query.setString(2, javaId.toString());
query.executeUpdate();
} catch (SQLException exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request =
new LinkRequestImpl(javaUsername, javaId, createCode(), bedrockUsername);
activeLinkRequests.put(javaUsername, request);
return request.getLinkCode();
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = activeLinkRequests.get(javaUsername);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
activeLinkRequests.remove(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
}

View File

@@ -1,3 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.SqliteDatabase"
}

View File

@@ -3,4 +3,4 @@ org.gradle.caching=true
org.gradle.parallel=true
version=2.2.2-SNAPSHOT
micronautVersion=3.8.7
micronautVersion=3.9.0

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

28
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright <EFBFBD> 2015-2021 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -0,0 +1,7 @@
plugins {
id("floodgate.base-conventions")
}
dependencies {
api(projects.api)
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library;
package org.geysermc.floodgate.isolation.library;
import java.nio.file.Path;
import java.security.MessageDigest;
@@ -32,25 +32,27 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.core.library.info.DependencyInfo;
import org.geysermc.floodgate.core.library.info.DependencyInfoLoader;
import org.geysermc.floodgate.core.util.Utils;
import org.geysermc.floodgate.isolation.library.info.DependencyInfo;
import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader;
public record Library(
@NonNull String id,
@NonNull Repository repository,
@NonNull String groupId,
String groupId,
@NonNull String artifactId,
@NonNull String version,
byte[] sha256
String version,
byte[] sha256,
boolean forceOverride
) {
public Library {
Objects.requireNonNull(id);
Objects.requireNonNull(repository);
Objects.requireNonNull(groupId);
Objects.requireNonNull(artifactId);
if (repository != Repository.BUNDLED) {
Objects.requireNonNull(groupId);
Objects.requireNonNull(version);
}
}
public String path() {
return "%s/%s/%s/%s-%s.jar".formatted(
@@ -71,10 +73,18 @@ public record Library(
}
public Path filePath() {
return Path.of(id(), id() + "-" + version() + ".jar");
var fileName = id();
if (!forceOverride) {
fileName += "-" + version();
}
return Path.of(id(), fileName + ".jar");
}
public void validateChecksum(byte[] data) {
if (sha256 == null) {
return;
}
byte[] hash;
try {
hash = MessageDigest.getInstance("SHA-256").digest(data);
@@ -88,8 +98,8 @@ public record Library(
throw new IllegalStateException(String.format(
"Downloaded library hash (%s) didn't match expected hash (%s)!",
Utils.base64Encode(data),
Utils.base64Encode(sha256())
Base64.getEncoder().encodeToString(data),
Base64.getEncoder().encodeToString(sha256())
));
}
@@ -103,6 +113,7 @@ public record Library(
private String version;
private byte[] sha256;
private boolean forceOverride;
LibraryBuilder(DependencyInfoLoader info) {
this.dependencyInfoLoader = info;
@@ -142,18 +153,18 @@ public record Library(
return sha256(Base64.getDecoder().decode(sha256));
}
public LibraryBuilder forceOverride(boolean forceOverride) {
this.forceOverride = forceOverride;
return this;
}
public Library build() {
if (version == null || sha256 == null) {
if (dependencyInfoLoader == null) {
throw new IllegalStateException(
"Provide a version and hash or provide a DependencyInfoLoader"
);
if (dependencyInfoLoader != null) {
DependencyInfo info = dependencyInfoLoader.byCombinedId(groupId, artifactId);
version = info.version();
sha256 = info.sha256();
}
DependencyInfo dependencyInfo = dependencyInfoLoader.byCombinedId(groupId, artifactId);
version = dependencyInfo.version();
sha256 = dependencyInfo.sha256();
}
return new Library(id, repository, groupId, artifactId, version, sha256);
return new Library(id, repository, groupId, artifactId, version, sha256, forceOverride);
}
}
}

View File

@@ -23,14 +23,15 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library;
package org.geysermc.floodgate.isolation.library;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.geysermc.floodgate.core.library.classloader.LibraryClassLoader;
import org.geysermc.floodgate.isolation.library.classloader.LibraryClassLoader;
import org.geysermc.floodgate.isolation.util.ChildFirstClassLoader;
// Credits for the idea of downloading dependencies on runtime go to LuckPerms
public final class LibraryManager {
@@ -39,8 +40,10 @@ public final class LibraryManager {
private final Set<Library> toApply = new HashSet<>();
public LibraryManager(ClassLoader parent, Path cacheDirectory) {
this.classLoader = new LibraryClassLoader(parent);
public LibraryManager(ClassLoader parent, Path cacheDirectory, boolean childFirst) {
this.classLoader = childFirst ?
new ChildFirstClassLoader(parent) :
new LibraryClassLoader(parent);
this.cacheDirectory = cacheDirectory;
}
@@ -49,12 +52,14 @@ public final class LibraryManager {
return this;
}
public void apply() {
public LibraryManager apply() {
CompletableFuture.allOf(
toApply.stream()
.map(library -> CompletableFuture.runAsync(() -> loadLibrary(library)))
.toArray(CompletableFuture[]::new)
).join();
toApply.clear();
return this;
}
private void loadLibrary(Library library) {
@@ -63,10 +68,14 @@ public final class LibraryManager {
return;
}
if (!Files.exists(libPath)) {
if (library.forceOverride() || !Files.exists(libPath)) {
library.repository().downloadTo(library, libPath);
}
classLoader.addPath(libPath);
}
public LibraryClassLoader classLoader() {
return classLoader;
}
}

View File

@@ -23,16 +23,32 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library;
package org.geysermc.floodgate.isolation.library;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.geysermc.floodgate.core.util.HttpClient;
import org.geysermc.floodgate.core.util.HttpClient.HttpResponse;
import org.geysermc.floodgate.isolation.util.HttpUtil;
import org.geysermc.floodgate.isolation.util.HttpUtil.HttpResponse;
import org.geysermc.floodgate.isolation.util.StreamUtil;
public enum Repository {
MAVEN_CENTRAL("https://repo1.maven.org/maven2/");
MAVEN_CENTRAL("https://repo1.maven.org/maven2/"),
OPEN_COLLAB("https://repo.opencollab.dev/main/"),
BUNDLED(null) {
@Override
public byte[] download(Library library) {
var loader = getClass().getClassLoader();
try (var resource = loader.getResourceAsStream("bundled/" + library.id() + ".jar")) {
return StreamUtil.readStream(resource);
} catch (Exception exception) {
throw new IllegalStateException(
"Could not load bundled jar " + library.id(),
exception
);
}
}
};
private final String baseUrl;
@@ -42,16 +58,16 @@ public enum Repository {
private byte[] justDownload(Library library) {
try {
HttpResponse<byte[]> response = new HttpClient().getRawData(baseUrl + library.path());
HttpResponse<byte[]> result = HttpUtil.getRawData(baseUrl + library.path());
if (!response.isCodeOk()) {
if (!result.isCodeOk()) {
throw new RuntimeException(String.format(
"Got an invalid response code (%s) while downloading library %s",
response.getHttpCode(), library.id()
result.httpCode(), library.id()
));
}
return response.getResponse();
return result.response();
} catch (IOException exception) {
throw new IllegalStateException("Failed to download library " + library.id(), exception);
}
@@ -65,6 +81,22 @@ public enum Repository {
public void downloadTo(Library library, Path location) {
try {
// delete old versions of a given library
if (Files.isDirectory(location.getParent())) {
try (var list = Files.list(location.getParent())) {
list.forEach(path -> {
try {
Files.delete(path);
} catch (IOException exception) {
throw new IllegalStateException(
"Could not delete old version of library " + library.id(),
exception
);
}
});
}
}
Files.createDirectories(location.getParent());
Files.write(location, download(library));
} catch (IOException exception) {

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library.classloader;
package org.geysermc.floodgate.isolation.library.classloader;
import java.net.MalformedURLException;
import java.net.URL;
@@ -49,8 +49,8 @@ public class LibraryClassLoader extends URLClassLoader {
try {
addURL(path.toUri().toURL());
loadedPaths.add(path);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
} catch (MalformedURLException exception) {
throw new IllegalArgumentException(exception);
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library.info;
package org.geysermc.floodgate.isolation.library.info;
import java.util.Base64;

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.library.info;
package org.geysermc.floodgate.isolation.library.info;
import java.io.BufferedReader;
import java.io.IOException;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,15 +23,32 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database.config;
package org.geysermc.floodgate.isolation.loader;
import lombok.Getter;
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
import java.lang.reflect.InvocationTargetException;
@Getter
public class MysqlConfig implements DatabaseConfig {
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
public class LoaderUtil {
public static void invokeLoad(Object platform) {
try {
platform.getClass().getMethod("load").invoke(platform);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
}
public static void invokeEnable(Object platform) {
try {
platform.getClass().getMethod("enable").invoke(platform);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
}
public static void invokeDisable(Object platform) {
try {
platform.getClass().getMethod("disable").invoke(platform);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.isolation.loader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.geysermc.floodgate.isolation.library.LibraryManager;
public class PlatformHolder {
private final Class<?> platformClass;
private final LibraryManager manager;
private Object platformInstance;
public PlatformHolder(Class<?> platformClass, LibraryManager manager) {
this.platformClass = platformClass;
this.manager = manager;
}
public void init(Class<?>[] argumentTypes, Object... argumentValues)
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
platformInstance = platformClass
.getConstructor(argumentTypes)
.newInstance(argumentValues);
}
public void load() {
LoaderUtil.invokeLoad(platformInstance);
}
public void enable() {
LoaderUtil.invokeEnable(platformInstance);
}
public void disable() {
LoaderUtil.invokeDisable(platformInstance);
close();
}
public void close() {
try {
manager.classLoader().close();
} catch (IOException exception) {
throw new IllegalStateException("Failed to close classloader!", exception);
}
}
public PlatformHolder platformInstance(Object platformInstance) {
if (this.platformInstance == null) {
this.platformInstance = platformInstance;
}
return this;
}
public LibraryManager manager() {
return manager;
}
public Class<?> platformClass() {
return platformClass;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.isolation.loader;
import java.nio.file.Path;
import org.geysermc.floodgate.isolation.library.Library;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.library.Repository;
import org.geysermc.floodgate.isolation.util.UrlUtil;
public class PlatformLoader {
public static LibraryManager createLibraryManager(ClassLoader loader, Path cacheDirectory) {
return new LibraryManager(loader, cacheDirectory, true)
.addLibrary(
Library.builder()
.id("platform-base")
.repository(Repository.BUNDLED)
.artifactId("platform-base")
.forceOverride(true)
.build()
)
.apply();
}
public static PlatformHolder load(LibraryManager manager) {
var classLoader = manager.classLoader();
String mainClassString =
UrlUtil.readSingleLine(classLoader.getResource("org.geysermc.mainClass"));
try {
var mainClass = classLoader.loadClass(mainClassString);
return new PlatformHolder(mainClass, manager);
} catch (Exception exception) {
throw new IllegalStateException("Failed to load platform!", exception);
}
}
public static PlatformHolder loadDefault(ClassLoader pluginClassLoader, Path cacheDirectory) {
return load(createLibraryManager(pluginClassLoader, cacheDirectory));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.isolation.util;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import org.geysermc.floodgate.isolation.library.classloader.LibraryClassLoader;
// Based of a Medium article https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305
public class ChildFirstClassLoader extends LibraryClassLoader {
private final ClassLoader systemClassLoader;
public ChildFirstClassLoader(ClassLoader parent) {
super(Objects.requireNonNull(parent));
systemClassLoader = getSystemClassLoader();
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
if (systemClassLoader != null) {
loadedClass = systemClassLoader.loadClass(name);
}
} catch (ClassNotFoundException ignored) {}
try {
if (loadedClass == null) {
loadedClass = findClass(name);
}
} catch (ClassNotFoundException e) {
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> allResources = new LinkedList<>();
Enumeration<URL> systemResources = systemClassLoader.getResources(name);
if (systemResources != null) {
while (systemResources.hasMoreElements()) {
allResources.add(systemResources.nextElement());
}
}
Enumeration<URL> thisResources = findResources(name);
if (thisResources != null) {
while (thisResources.hasMoreElements()) {
allResources.add(thisResources.nextElement());
}
}
Enumeration<URL> parentResources = getParent().getResources(name);
if (parentResources != null) {
while (parentResources.hasMoreElements()) {
allResources.add(parentResources.nextElement());
}
}
return new Enumeration<>() {
final Iterator<URL> it = allResources.iterator();
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public URL nextElement() {
return it.next();
}
};
}
@Override
public URL getResource(String name) {
URL resource = null;
if (systemClassLoader != null) {
resource = systemClassLoader.getResource(name);
}
if (resource == null) {
resource = findResource(name);
}
if (resource == null) {
resource = getParent().getResource(name);
}
return resource;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.isolation.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import org.checkerframework.checker.nullness.qual.NonNull;
public class HttpUtil {
private static final String USER_AGENT = "GeyserMC-isolation";
public static @NonNull HttpResponse<byte[]> getRawData(String urlString) throws IOException {
HttpURLConnection connection = request(urlString);
try (InputStream inputStream = connection.getInputStream()) {
return new HttpResponse<>(
connection.getResponseCode(),
StreamUtil.readStream(inputStream)
);
} catch (SocketTimeoutException | NullPointerException exception) {
return new HttpResponse<>(-1, null);
}
}
private static HttpURLConnection request(String urlString) {
HttpURLConnection connection;
try {
URL url = new URL(urlString.replace(" ", "%20")); // Encode spaces correctly
connection = (HttpURLConnection) url.openConnection();
} catch (Exception exception) {
throw new RuntimeException("Failed to create connection", exception);
}
try {
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setRequestProperty("User-Agent", USER_AGENT);
connection.setConnectTimeout(3000);
connection.setReadTimeout(10_000);
} catch (Exception exception) {
throw new RuntimeException("Failed to create request", exception);
}
return connection;
}
public static class HttpResponse<T> {
private final int httpCode;
private final T response;
private HttpResponse(int httpCode, T response) {
this.httpCode = httpCode;
this.response = response;
}
public int httpCode() {
return httpCode;
}
public T response() {
return response;
}
public boolean isCodeOk() {
return httpCode >= 200 && httpCode < 300;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,16 +23,21 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database.config;
package org.geysermc.floodgate.isolation.util;
import lombok.Getter;
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@Getter
public class MongoConfig implements DatabaseConfig {
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
private String mongouri = "";
public class StreamUtil {
public static byte[] readStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[8196];
int len;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer, 0, len);
}
return outputStream.toByteArray();
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.isolation.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class UrlUtil {
public static List<String> readAllLines(URL url) {
try {
var connection = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
var list = new ArrayList<String>();
String line;
while ((line = reader.readLine()) != null) {
list.add(line);
}
reader.close();
return list;
} catch (IOException exception) {
throw new IllegalStateException("Unable to read url %s".formatted(url), exception);
}
}
public static String readSingleLine(URL url) {
var lines = readAllLines(url);
if (lines.size() != 1) {
throw new IllegalStateException(
"Url %s didn't return one line of data, got %s".formatted(url, lines.size())
);
}
return lines.get(0);
}
}

View File

@@ -68,11 +68,16 @@ include(":api")
include(":core")
include(":bungee")
include(":spigot")
include(":velocity")
include(":universal")
include(":sqlite")
include(":mysql")
include(":mongo")
project(":sqlite").projectDir = file("database/sqlite")
project(":mysql").projectDir = file("database/mysql")
project(":mongo").projectDir = file("database/mongo")
include(":database")
include(":isolation")
arrayOf("velocity").forEach { platform ->
arrayOf("base", "isolated").forEach {
include(":$platform-$it")
project(":$platform-$it").projectDir = file("$platform/$it")
}
}

View File

@@ -35,6 +35,7 @@ import io.micronaut.inject.qualifiers.Qualifiers;
import java.nio.file.Path;
import org.geysermc.floodgate.core.FloodgatePlatform;
import org.geysermc.floodgate.core.util.ReflectionUtils;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.slf4j.Logger;
public class VelocityPlatform extends FloodgatePlatform {
@@ -46,7 +47,9 @@ public class VelocityPlatform extends FloodgatePlatform {
@Inject PluginContainer container;
@Inject Logger logger;
public VelocityPlatform() {
@Inject
public VelocityPlatform(LibraryManager manager) {
super(manager);
ReflectionUtils.setPrefix("com.velocitypowered.proxy");
}

View File

@@ -0,0 +1,36 @@
var log4jVersion = "2.11.2"
var gsonVersion = "2.8.8"
var guavaVersion = "25.1-jre"
plugins {
java
}
dependencies {
api(projects.isolation)
}
tasks {
jar {
dependsOn(":velocity-base:build", configurations.runtimeClasspath)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from (configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
archiveBaseName.set("floodgate-${project.name}")
archiveVersion.set("")
archiveClassifier.set("")
val velocityBaseJar = project.projects
.velocityBase.dependencyProject
.buildDir
.resolve("libs")
.resolve("floodgate-velocity-base.jar")
from(velocityBaseJar.parentFile) {
include(velocityBaseJar.name)
rename("floodgate-velocity-base.jar", "platform-base.jar")
into("bundled/")
}
}
}

View File

@@ -25,28 +25,45 @@
package org.geysermc.floodgate.velocity;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import java.nio.file.Paths;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.loader.PlatformHolder;
import org.geysermc.floodgate.isolation.loader.PlatformLoader;
public final class VelocityPlugin {
private final VelocityPlatform platform;
public final class IsolatedVelocityPlugin {
private final PlatformHolder holder;
@Inject
public VelocityPlugin(Injector guice) {
platform = guice.getInstance(VelocityPlatform.class);
platform.load();
public IsolatedVelocityPlugin(Injector guice) {
try {
holder = PlatformLoader.loadDefault(getClass().getClassLoader(), Paths.get("./libs"));
Injector child = guice.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(LibraryManager.class).toInstance(holder.manager());
}
});
holder.platformInstance(child.getInstance(holder.platformClass()));
holder.load();
} catch (Exception exception) {
throw new RuntimeException("Failed to load Floodgate", exception);
}
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
platform.enable();
holder.enable();
}
@Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) {
platform.disable();
holder.disable();
}
}

View File

@@ -0,0 +1 @@
org.geysermc.floodgate.velocity.VelocityPlatform

View File

@@ -1 +1 @@
{"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "$url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.velocity.VelocityPlugin"}
{"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "$url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.velocity.IsolatedVelocityPlugin"}