Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-10-05 09:20:07 +02:00
Merge pull request #2761 from ImDaBigBoss/master
Geyser extension loading
Dieser Commit ist enthalten in:
Commit
3b088e81bc
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the Geyser extension description
|
||||||
|
*/
|
||||||
|
public interface ExtensionDescription {
|
||||||
|
/**
|
||||||
|
* Gets the extension's name
|
||||||
|
*
|
||||||
|
* @return the extension's name
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's main class
|
||||||
|
*
|
||||||
|
* @return the extension's main class
|
||||||
|
*/
|
||||||
|
String main();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's api version
|
||||||
|
*
|
||||||
|
* @return the extension's api version
|
||||||
|
*/
|
||||||
|
String apiVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's description
|
||||||
|
*
|
||||||
|
* @return the extension's description
|
||||||
|
*/
|
||||||
|
String version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's authors
|
||||||
|
*
|
||||||
|
* @return the extension's authors
|
||||||
|
*/
|
||||||
|
List<String> authors();
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extension loader is responsible for loading, unloading, enabling and disabling extensions
|
||||||
|
*/
|
||||||
|
public interface ExtensionLoader {
|
||||||
|
/**
|
||||||
|
* Loads an extension from a given file
|
||||||
|
*
|
||||||
|
* @param file the file to load the extension from
|
||||||
|
* @return the loaded extension
|
||||||
|
* @throws InvalidExtensionException
|
||||||
|
*/
|
||||||
|
GeyserExtension loadExtension(File file) throws InvalidExtensionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an extension's description from a given file
|
||||||
|
*
|
||||||
|
* @param file the file to get the description from
|
||||||
|
* @return the extension's description
|
||||||
|
* @throws InvalidDescriptionException
|
||||||
|
*/
|
||||||
|
ExtensionDescription extensionDescription(File file) throws InvalidDescriptionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a class by its name from the extension's classloader
|
||||||
|
*
|
||||||
|
* @param name the name of the class
|
||||||
|
* @return the class
|
||||||
|
* @throws ClassNotFoundException
|
||||||
|
*/
|
||||||
|
Class<?> classByName(final String name) throws ClassNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables an extension
|
||||||
|
*
|
||||||
|
* @param extension the extension to enable
|
||||||
|
*/
|
||||||
|
void enableExtension(GeyserExtension extension);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables an extension
|
||||||
|
*
|
||||||
|
* @param extension the extension to disable
|
||||||
|
*/
|
||||||
|
void disableExtension(GeyserExtension extension);
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the Geyser extension logger
|
||||||
|
*/
|
||||||
|
public interface ExtensionLogger {
|
||||||
|
/**
|
||||||
|
* Get the logger prefix
|
||||||
|
*
|
||||||
|
* @return the logger prefix
|
||||||
|
*/
|
||||||
|
String prefix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a severe message to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
*/
|
||||||
|
void severe(String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a severe message and an exception to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
* @param error the error to throw
|
||||||
|
*/
|
||||||
|
void severe(String message, Throwable error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error message to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
*/
|
||||||
|
void error(String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an error message and an exception to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
* @param error the error to throw
|
||||||
|
*/
|
||||||
|
void error(String message, Throwable error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a warning message to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
*/
|
||||||
|
void warning(String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an info message to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
*/
|
||||||
|
void info(String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a debug message to console
|
||||||
|
*
|
||||||
|
* @param message the message to log
|
||||||
|
*/
|
||||||
|
void debug(String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If debug is enabled for this logger
|
||||||
|
*/
|
||||||
|
boolean isDebug();
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension;
|
||||||
|
|
||||||
|
import org.geysermc.api.GeyserApiBase;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is to be extended by a Geyser extension
|
||||||
|
*/
|
||||||
|
public class GeyserExtension {
|
||||||
|
private boolean initialized = false;
|
||||||
|
private boolean enabled = false;
|
||||||
|
private File file = null;
|
||||||
|
private File dataFolder = null;
|
||||||
|
private ClassLoader classLoader = null;
|
||||||
|
private ExtensionLoader loader = null;
|
||||||
|
private ExtensionLogger logger = null;
|
||||||
|
private ExtensionDescription description = null;
|
||||||
|
private GeyserApiBase api = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the extension is loaded
|
||||||
|
*/
|
||||||
|
public void onLoad() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the extension is enabled
|
||||||
|
*/
|
||||||
|
public void onEnable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the extension is disabled
|
||||||
|
*/
|
||||||
|
public void onDisable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if the extension is enabled
|
||||||
|
*
|
||||||
|
* @return true if the extension is enabled
|
||||||
|
*/
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the extension
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean value) {
|
||||||
|
if (this.enabled != value) {
|
||||||
|
this.enabled = value;
|
||||||
|
if (this.enabled) {
|
||||||
|
onEnable();
|
||||||
|
} else {
|
||||||
|
onDisable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's data folder
|
||||||
|
*
|
||||||
|
* @return the extension's data folder
|
||||||
|
*/
|
||||||
|
public File dataFolder() {
|
||||||
|
return this.dataFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's description
|
||||||
|
*
|
||||||
|
* @return the extension's description
|
||||||
|
*/
|
||||||
|
public ExtensionDescription description() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's name
|
||||||
|
*
|
||||||
|
* @return the extension's name
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return this.description.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(GeyserApiBase api, ExtensionLoader loader, ExtensionLogger logger, ExtensionDescription description, File dataFolder, File file) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true;
|
||||||
|
this.file = file;
|
||||||
|
this.dataFolder = dataFolder;
|
||||||
|
this.classLoader = this.getClass().getClassLoader();
|
||||||
|
this.loader = loader;
|
||||||
|
this.logger = logger;
|
||||||
|
this.description = description;
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a resource from the extension jar file
|
||||||
|
*
|
||||||
|
* @param filename the file name
|
||||||
|
* @return the input stream
|
||||||
|
*/
|
||||||
|
public InputStream getResource(String filename) {
|
||||||
|
if (filename == null) {
|
||||||
|
throw new IllegalArgumentException("Filename cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
URL url = this.classLoader.getResource(filename);
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
return connection.getInputStream();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a resource from the extension jar file to the extension's data folder
|
||||||
|
*
|
||||||
|
* @param filename the file name
|
||||||
|
* @param replace whether to replace the file if it already exists
|
||||||
|
*/
|
||||||
|
public void saveResource(String filename, boolean replace) {
|
||||||
|
if (filename == null || filename.equals("")) {
|
||||||
|
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = filename.replace('\\', '/');
|
||||||
|
InputStream in = getResource(filename);
|
||||||
|
if (in == null) {
|
||||||
|
throw new IllegalArgumentException("The embedded resource '" + filename + "' cannot be found in " + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
File outFile = new File(dataFolder, filename);
|
||||||
|
int lastIndex = filename.lastIndexOf('/');
|
||||||
|
File outDir = new File(dataFolder, filename.substring(0, Math.max(lastIndex, 0)));
|
||||||
|
|
||||||
|
if (!outDir.exists()) {
|
||||||
|
outDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!outFile.exists() || replace) {
|
||||||
|
OutputStream out = new FileOutputStream(outFile);
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buf)) > 0) {
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
in.close();
|
||||||
|
} else {
|
||||||
|
this.logger.warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists.");
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
this.logger.severe("Could not save " + outFile.getName() + " to " + outFile, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's class loader
|
||||||
|
*
|
||||||
|
* @return the extension's class loader
|
||||||
|
*/
|
||||||
|
public ClassLoader classLoader() {
|
||||||
|
return this.classLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's loader
|
||||||
|
*
|
||||||
|
* @return the extension's loader
|
||||||
|
*/
|
||||||
|
public ExtensionLoader extensionLoader() {
|
||||||
|
return this.loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the extension's logger
|
||||||
|
*
|
||||||
|
* @return the extension's logger
|
||||||
|
*/
|
||||||
|
public ExtensionLogger logger() {
|
||||||
|
return this.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link GeyserApiBase} instance
|
||||||
|
*
|
||||||
|
* @return the {@link GeyserApiBase} instance
|
||||||
|
*/
|
||||||
|
public GeyserApiBase geyserApi() {
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an extension's description is invalid.
|
||||||
|
*/
|
||||||
|
public class InvalidDescriptionException extends Exception {
|
||||||
|
public InvalidDescriptionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDescriptionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDescriptionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.api.extension.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an extension is invalid.
|
||||||
|
*/
|
||||||
|
public class InvalidExtensionException extends Exception {
|
||||||
|
public InvalidExtensionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidExtensionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidExtensionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,7 @@ import org.geysermc.geyser.api.GeyserApi;
|
|||||||
import org.geysermc.geyser.command.CommandManager;
|
import org.geysermc.geyser.command.CommandManager;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
|
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||||
import org.geysermc.geyser.level.WorldManager;
|
import org.geysermc.geyser.level.WorldManager;
|
||||||
import org.geysermc.geyser.network.ConnectorServerEventHandler;
|
import org.geysermc.geyser.network.ConnectorServerEventHandler;
|
||||||
import org.geysermc.geyser.pack.ResourcePack;
|
import org.geysermc.geyser.pack.ResourcePack;
|
||||||
@ -122,6 +123,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
private final PlatformType platformType;
|
private final PlatformType platformType;
|
||||||
private final GeyserBootstrap bootstrap;
|
private final GeyserBootstrap bootstrap;
|
||||||
|
|
||||||
|
private final GeyserExtensionManager extensionManager;
|
||||||
|
|
||||||
private Metrics metrics;
|
private Metrics metrics;
|
||||||
|
|
||||||
private static GeyserImpl instance;
|
private static GeyserImpl instance;
|
||||||
@ -154,6 +157,9 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
MessageTranslator.init();
|
MessageTranslator.init();
|
||||||
MinecraftLocale.init();
|
MinecraftLocale.init();
|
||||||
|
|
||||||
|
extensionManager = new GeyserExtensionManager();
|
||||||
|
extensionManager.init();
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
||||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||||
@ -197,6 +203,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
|
|
||||||
ResourcePack.loadPacks();
|
ResourcePack.loadPacks();
|
||||||
|
|
||||||
|
extensionManager.enableExtensions();
|
||||||
|
|
||||||
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
|
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
|
||||||
// Set the remote address to localhost since that is where we are always connecting
|
// Set the remote address to localhost since that is where we are always connecting
|
||||||
try {
|
try {
|
||||||
@ -457,6 +465,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
|
|
||||||
ResourcePack.PACKS.clear();
|
ResourcePack.PACKS.clear();
|
||||||
|
|
||||||
|
extensionManager.disableExtensions();
|
||||||
|
|
||||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
|
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,6 +518,10 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
return bootstrap.getWorldManager();
|
return bootstrap.getWorldManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GeyserExtensionManager getExtensionManager() {
|
||||||
|
return extensionManager;
|
||||||
|
}
|
||||||
|
|
||||||
public static GeyserImpl getInstance() {
|
public static GeyserImpl getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.extension.GeyserExtension;
|
||||||
|
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.geyser.network.MinecraftProtocol;
|
import org.geysermc.geyser.network.MinecraftProtocol;
|
||||||
@ -54,10 +56,7 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -76,6 +75,7 @@ public class DumpInfo {
|
|||||||
private LogsInfo logsInfo;
|
private LogsInfo logsInfo;
|
||||||
private final BootstrapDumpInfo bootstrapInfo;
|
private final BootstrapDumpInfo bootstrapInfo;
|
||||||
private final FlagsInfo flagsInfo;
|
private final FlagsInfo flagsInfo;
|
||||||
|
private final List<ExtensionInfo> extensionInfo;
|
||||||
|
|
||||||
public DumpInfo(boolean addLog) {
|
public DumpInfo(boolean addLog) {
|
||||||
this.versionInfo = new VersionInfo();
|
this.versionInfo = new VersionInfo();
|
||||||
@ -125,6 +125,11 @@ public class DumpInfo {
|
|||||||
this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo();
|
this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo();
|
||||||
|
|
||||||
this.flagsInfo = new FlagsInfo();
|
this.flagsInfo = new FlagsInfo();
|
||||||
|
|
||||||
|
this.extensionInfo = new ArrayList<>();
|
||||||
|
for (GeyserExtension extension : GeyserImpl.getInstance().getExtensionManager().getExtensions().values()) {
|
||||||
|
this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -277,4 +282,15 @@ public class DumpInfo {
|
|||||||
this.flags = ManagementFactory.getRuntimeMXBean().getInputArguments();
|
this.flags = ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class ExtensionInfo {
|
||||||
|
public boolean enabled;
|
||||||
|
public String name;
|
||||||
|
public String version;
|
||||||
|
public String apiVersion;
|
||||||
|
public String main;
|
||||||
|
public List<String> authors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.extension;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.api.extension.ExtensionDescription;
|
||||||
|
import org.geysermc.geyser.api.extension.GeyserExtension;
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class GeyserExtensionClassLoader extends URLClassLoader {
|
||||||
|
private final GeyserExtensionLoader loader;
|
||||||
|
private final Map<String, Class> classes = new HashMap<>();
|
||||||
|
public GeyserExtension extension;
|
||||||
|
|
||||||
|
public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException {
|
||||||
|
super(new URL[] { file.toURI().toURL() }, parent);
|
||||||
|
this.loader = loader;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> jarClass;
|
||||||
|
try {
|
||||||
|
jarClass = Class.forName(description.main(), true, this);
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
throw new InvalidExtensionException("Class " + description.main() + " not found, extension cannot be loaded", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends GeyserExtension> extensionClass;
|
||||||
|
try {
|
||||||
|
extensionClass = jarClass.asSubclass(GeyserExtension.class);
|
||||||
|
} catch (ClassCastException ex) {
|
||||||
|
throw new InvalidExtensionException("Main class " + description.main() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension = extensionClass.newInstance();
|
||||||
|
} catch (IllegalAccessException ex) {
|
||||||
|
throw new InvalidExtensionException("No public constructor", ex);
|
||||||
|
} catch (InstantiationException ex) {
|
||||||
|
throw new InvalidExtensionException("Abnormal extension type", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
return this.findClass(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class<?> findClass(String name, boolean checkGlobal) throws ClassNotFoundException {
|
||||||
|
if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("org.geysermc.platform.") || name.startsWith("org.geysermc.floodgate.") || name.startsWith("org.geysermc.api.") || name.startsWith("org.geysermc.processor.") || name.startsWith("net.minecraft.")) {
|
||||||
|
throw new ClassNotFoundException(name);
|
||||||
|
}
|
||||||
|
Class<?> result = classes.get(name);
|
||||||
|
if (result == null) {
|
||||||
|
if (checkGlobal) {
|
||||||
|
result = loader.classByName(name);
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = super.findClass(name);
|
||||||
|
if (result != null) {
|
||||||
|
loader.setClass(name, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classes.put(name, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> getClasses() {
|
||||||
|
return classes.keySet();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.extension;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
|
||||||
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription {
|
||||||
|
private String name;
|
||||||
|
private String main;
|
||||||
|
private String api;
|
||||||
|
private String version;
|
||||||
|
private final List<String> authors = new ArrayList<>();
|
||||||
|
|
||||||
|
public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescriptionException {
|
||||||
|
DumperOptions dumperOptions = new DumperOptions();
|
||||||
|
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
|
Yaml yaml = new Yaml(dumperOptions);
|
||||||
|
this.loadMap(yaml.loadAs(inputStream, LinkedHashMap.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMap(Map<String, Object> yamlMap) throws InvalidDescriptionException {
|
||||||
|
this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", "");
|
||||||
|
if (this.name.equals("")) {
|
||||||
|
throw new InvalidDescriptionException("Invalid extension name, cannot be empty");
|
||||||
|
}
|
||||||
|
this.name = this.name.replace(" ", "_");
|
||||||
|
this.version = String.valueOf(yamlMap.get("version"));
|
||||||
|
this.main = (String) yamlMap.get("main");
|
||||||
|
|
||||||
|
Object api = yamlMap.get("api");
|
||||||
|
if (api instanceof String) {
|
||||||
|
this.api = (String) api;
|
||||||
|
} else {
|
||||||
|
this.api = "0.0.0";
|
||||||
|
throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yamlMap.containsKey("author")) {
|
||||||
|
this.authors.add((String) yamlMap.get("author"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yamlMap.containsKey("authors")) {
|
||||||
|
try {
|
||||||
|
this.authors.addAll((Collection<? extends String>) yamlMap.get("authors"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String main() {
|
||||||
|
return this.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apiVersion() {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String version() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> authors() {
|
||||||
|
return this.authors;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.extension;
|
||||||
|
|
||||||
|
import org.geysermc.api.Geyser;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.extension.ExtensionLoader;
|
||||||
|
import org.geysermc.geyser.api.extension.GeyserExtension;
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
|
||||||
|
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
|
||||||
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class GeyserExtensionLoader implements ExtensionLoader {
|
||||||
|
private final Map<String, Class> classes = new HashMap<>();
|
||||||
|
private final Map<String, GeyserExtensionClassLoader> classLoaders = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GeyserExtension loadExtension(File file) throws InvalidExtensionException {
|
||||||
|
if (file == null) {
|
||||||
|
throw new InvalidExtensionException("File is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
final GeyserExtensionDescription description;
|
||||||
|
try {
|
||||||
|
description = extensionDescription(file);
|
||||||
|
} catch (InvalidDescriptionException e) {
|
||||||
|
throw new InvalidExtensionException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final File parentFile = file.getParentFile();
|
||||||
|
final File dataFolder = new File(parentFile, description.name());
|
||||||
|
if (dataFolder.exists() && !dataFolder.isDirectory()) {
|
||||||
|
throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.name() + "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
final GeyserExtensionClassLoader loader;
|
||||||
|
try {
|
||||||
|
loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, file);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new InvalidExtensionException(e);
|
||||||
|
}
|
||||||
|
classLoaders.put(description.name(), loader);
|
||||||
|
|
||||||
|
setup(loader.extension, description, dataFolder, file);
|
||||||
|
return loader.extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup(GeyserExtension extension, GeyserExtensionDescription description, File dataFolder, File file) {
|
||||||
|
GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name());
|
||||||
|
extension.init(Geyser.api(), this, logger, description, dataFolder, file);
|
||||||
|
extension.onLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GeyserExtensionDescription extensionDescription(File file) throws InvalidDescriptionException {
|
||||||
|
JarFile jarFile = null;
|
||||||
|
InputStream stream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
jarFile = new JarFile(file);
|
||||||
|
|
||||||
|
JarEntry descriptionEntry = jarFile.getJarEntry("extension.yml");
|
||||||
|
if (descriptionEntry == null) {
|
||||||
|
throw new InvalidDescriptionException(new FileNotFoundException("extension.yml") + " does not exist in the jar file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = jarFile.getInputStream(descriptionEntry);
|
||||||
|
return new GeyserExtensionDescription(stream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidDescriptionException(e);
|
||||||
|
} finally {
|
||||||
|
if (jarFile != null) {
|
||||||
|
try {
|
||||||
|
jarFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern[] extensionFilters() {
|
||||||
|
return new Pattern[] { Pattern.compile("^.+\\.jar$") };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> classByName(final String name) throws ClassNotFoundException{
|
||||||
|
Class<?> clazz = classes.get(name);
|
||||||
|
try {
|
||||||
|
for(GeyserExtensionClassLoader loader : classLoaders.values()) {
|
||||||
|
try {
|
||||||
|
clazz = loader.findClass(name,false);
|
||||||
|
} catch(NullPointerException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
} catch(NullPointerException s) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setClass(String name, final Class<?> clazz) {
|
||||||
|
if (!classes.containsKey(name)) {
|
||||||
|
classes.put(name,clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeClass(String name) {
|
||||||
|
classes.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableExtension(GeyserExtension extension) {
|
||||||
|
if (!extension.isEnabled()) {
|
||||||
|
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name()));
|
||||||
|
extension.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableExtension(GeyserExtension extension) {
|
||||||
|
if (extension.isEnabled()) {
|
||||||
|
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name()));
|
||||||
|
extension.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.extension;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.GeyserLogger;
|
||||||
|
import org.geysermc.geyser.api.extension.ExtensionLogger;
|
||||||
|
|
||||||
|
public class GeyserExtensionLogger implements ExtensionLogger {
|
||||||
|
private final GeyserLogger logger;
|
||||||
|
private final String loggerPrefix;
|
||||||
|
|
||||||
|
public GeyserExtensionLogger(GeyserLogger logger, String prefix) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.loggerPrefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String prefix() {
|
||||||
|
return this.loggerPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String addPrefix(String message) {
|
||||||
|
return "[" + this.loggerPrefix + "] " + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void severe(String message) {
|
||||||
|
this.logger.severe(this.addPrefix(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void severe(String message, Throwable error) {
|
||||||
|
this.logger.severe(this.addPrefix(message), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String message) {
|
||||||
|
this.logger.error(this.addPrefix(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String message, Throwable error) {
|
||||||
|
this.logger.error(this.addPrefix(message), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warning(String message) {
|
||||||
|
this.logger.warning(this.addPrefix(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String message) {
|
||||||
|
this.logger.info(this.addPrefix(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String message) {
|
||||||
|
this.logger.debug(this.addPrefix(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebug() {
|
||||||
|
return this.logger.isDebug();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* 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/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.extension;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.api.extension.ExtensionDescription;
|
||||||
|
import org.geysermc.geyser.api.extension.GeyserExtension;
|
||||||
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class GeyserExtensionManager {
|
||||||
|
protected Map<String, GeyserExtension> extensions = new LinkedHashMap<>();
|
||||||
|
protected Map<Pattern, GeyserExtensionLoader> fileAssociations = new HashMap<>();
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading"));
|
||||||
|
|
||||||
|
this.registerInterface(GeyserExtensionLoader.class);
|
||||||
|
this.loadExtensions(new File("extensions"));
|
||||||
|
|
||||||
|
GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeyserExtension getExtension(String name) {
|
||||||
|
if (this.extensions.containsKey(name)) {
|
||||||
|
return this.extensions.get(name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, GeyserExtension> getExtensions() {
|
||||||
|
return this.extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerInterface(Class<? extends GeyserExtensionLoader> loader) {
|
||||||
|
GeyserExtensionLoader instance;
|
||||||
|
|
||||||
|
if (GeyserExtensionLoader.class.isAssignableFrom(loader)) {
|
||||||
|
Constructor<? extends GeyserExtensionLoader> constructor;
|
||||||
|
|
||||||
|
try {
|
||||||
|
constructor = loader.getConstructor();
|
||||||
|
instance = constructor.newInstance();
|
||||||
|
} catch (NoSuchMethodException ex) { // This should never happen
|
||||||
|
String className = loader.getName();
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Class " + className + " does not have a public constructor", ex);
|
||||||
|
} catch (Exception ex) { // This should never happen
|
||||||
|
throw new IllegalArgumentException("Unexpected exception " + ex.getClass().getName() + " while attempting to construct a new instance of " + loader.getName(), ex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader");
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern[] patterns = instance.extensionFilters();
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
for (Pattern pattern : patterns) {
|
||||||
|
fileAssociations.put(pattern, instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeyserExtension loadExtension(File file, Map<Pattern, GeyserExtensionLoader> loaders) {
|
||||||
|
for (GeyserExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) {
|
||||||
|
for (Pattern pattern : loader.extensionFilters()) {
|
||||||
|
if (pattern.matcher(file.getName()).matches()) {
|
||||||
|
try {
|
||||||
|
ExtensionDescription description = loader.extensionDescription(file);
|
||||||
|
if (description != null) {
|
||||||
|
GeyserExtension extension = loader.loadExtension(file);
|
||||||
|
|
||||||
|
if (extension != null) {
|
||||||
|
this.extensions.put(extension.description().name(), extension);
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed"), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, GeyserExtension> loadExtensions(File dictionary) {
|
||||||
|
if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { // If your IDE says this is always true, ignore it, it isn't.
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment"));
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
if (!GeyserImpl.VERSION.contains(".")) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number"));
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] apiVersion = GeyserImpl.VERSION.split("\\.");
|
||||||
|
|
||||||
|
if (!dictionary.exists()) {
|
||||||
|
dictionary.mkdir();
|
||||||
|
}
|
||||||
|
if (!dictionary.isDirectory()) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, File> extensions = new LinkedHashMap<>();
|
||||||
|
Map<String, GeyserExtension> loadedExtensions = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (final GeyserExtensionLoader loader : this.fileAssociations.values()) {
|
||||||
|
for (File file : dictionary.listFiles((dir, name) -> {
|
||||||
|
for (Pattern pattern : loader.extensionFilters()) {
|
||||||
|
if (pattern.matcher(name).matches()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExtensionDescription description = loader.extensionDescription(file);
|
||||||
|
if (description != null) {
|
||||||
|
String name = description.name();
|
||||||
|
|
||||||
|
if (extensions.containsKey(name) || this.getExtension(name) != null) {
|
||||||
|
GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, file.getName()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Check the format: majorVersion.minorVersion.patch
|
||||||
|
if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.apiVersion())) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
} catch (NullPointerException | IllegalArgumentException e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] versionArray = description.apiVersion().split("\\.");
|
||||||
|
|
||||||
|
//Completely different API version
|
||||||
|
if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the extension requires new API features, being backwards compatible
|
||||||
|
if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.put(name, file);
|
||||||
|
loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", file.getName(), dictionary.getAbsolutePath()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadedExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableExtension(GeyserExtension extension) {
|
||||||
|
if (!extension.isEnabled()) {
|
||||||
|
try {
|
||||||
|
extension.extensionLoader().enableExtension(extension);
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e);
|
||||||
|
this.disableExtension(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableExtension(GeyserExtension extension) {
|
||||||
|
if (extension.isEnabled()) {
|
||||||
|
try {
|
||||||
|
extension.extensionLoader().disableExtension(extension);
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.failed", extension.name()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableExtensions() {
|
||||||
|
for (GeyserExtension extension : this.getExtensions().values()) {
|
||||||
|
this.enableExtension(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableExtensions() {
|
||||||
|
for (GeyserExtension extension : this.getExtensions().values()) {
|
||||||
|
this.disableExtension(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit bdee0d0f3f8a1271cd001f0bd0d672d0010be1db
|
Subproject commit 94c1851931f2319a7e7f42c2fe9066b78235bc39
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren