diff --git a/LaunchServer/resources/launchserver/defaults/config.cfg b/LaunchServer/resources/launchserver/defaults/config.cfg index 5575f3b..aac93d2 100644 --- a/LaunchServer/resources/launchserver/defaults/config.cfg +++ b/LaunchServer/resources/launchserver/defaults/config.cfg @@ -2,10 +2,6 @@ bindAddress: "0.0.0.0"; port: 7240; -# Textures (for clientside) -skinsURL: "http://skins.minecraft.net/MinecraftSkins/%username%.png"; -cloaksURL: "http://skins.minecraft.net/MinecraftCloaks/%username%.png"; - # Auth handler authHandler: "textFile"; authHandlerConfig: { @@ -19,5 +15,12 @@ message: "You need to change auth provider in LaunchServer.cfg"; }; +# Texture provider +textureProvider: "request"; +textureProviderConfig: { + skinsURL: "http://skins.minecraft.net/MinecraftSkins/%username%.png"; + cloaksURL: "http://skins.minecraft.net/MinecraftCloaks/%username%.png"; +} + # Launch4J EXE binary building launch4J: false; diff --git a/LaunchServer/runtime/apiServer.js b/LaunchServer/runtime/apiServer.js index 2f74136..6868cb1 100644 --- a/LaunchServer/runtime/apiServer.js +++ b/LaunchServer/runtime/apiServer.js @@ -8,6 +8,7 @@ var AuthProvider = AuthProviderClass.static; var DigestAuthProvider = DigestAuthProviderClass.static; var AuthException = AuthExceptionClass.static; +var TextureProvider = TextureProviderClass.static; // Command class API imports var Command = CommandClass.static; diff --git a/LaunchServer/runtime/plugin.js b/LaunchServer/runtime/plugin.js index 63b952d..fcf739d 100644 --- a/LaunchServer/runtime/plugin.js +++ b/LaunchServer/runtime/plugin.js @@ -5,31 +5,35 @@ // Register command server.commandHandler.registerCommand("test", new (Java.extend(Command, { - getArgsDescription: function() { return "[anything]"; }, - getUsageDescription: function() { return "plugin.js test command"; }, + getArgsDescription: function () { + return "[anything]"; + }, + getUsageDescription: function () { + return "plugin.js test command"; + }, - invoke: function(args) { + invoke: function (args) { LogHelper.info("[plugin.js] Command invoked! Args: " + java.util.Arrays.toString(args)); } }))(server)); // Register custom response -server.serverSocketHandler.registerCustomResponse("test", function(server, id, input, output) { - return new (Java.extend(Response, function() { +server.serverSocketHandler.registerCustomResponse("test", function (server, id, input, output) { + return new (Java.extend(Response, function () { LogHelper.info("[plugin.js] Custom response invoked!"); output.writeInt(0x724); }))(server, id, input, output); }); /* You can test custom request like this: -var TestCustomRequest = Java.extend(CustomRequest, { - getName: function() { return "test"; }, + var TestCustomRequest = Java.extend(CustomRequest, { + getName: function() { return "test"; }, - requestDoCustom: function(input, output) { - return input.readInt(); - } -}); -var answer = new TestCustomRequest().request(); -LogHelper.info(Integer.toHexString(answer)); -*/ + requestDoCustom: function(input, output) { + return input.readInt(); + } + }); + var answer = new TestCustomRequest().request(); + LogHelper.info(Integer.toHexString(answer)); + */ diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index b6ab303..f82deef 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -6,7 +6,6 @@ import javax.script.ScriptException; import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -30,15 +29,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; import launcher.Launcher; import launcher.LauncherAPI; -import launcher.client.ClientLauncher; import launcher.client.ClientProfile; -import launcher.client.PlayerProfile; import launcher.hasher.HashedDir; import launcher.helper.CommonHelper; import launcher.helper.IOHelper; @@ -70,6 +66,7 @@ import launchserver.command.handler.StdCommandHandler; import launchserver.response.Response; import launchserver.response.ServerSocketHandler; +import launchserver.texture.TextureProvider; public final class LaunchServer implements Runnable { // Constant paths @@ -370,6 +367,7 @@ bindings.put("AuthProviderClass", AuthProvider.class); bindings.put("DigestAuthProviderClass", AuthProvider.class); bindings.put("AuthExceptionClass", AuthException.class); + bindings.put("TextureProviderClass", TextureProvider.class); // Set command class bindings bindings.put("CommandClass", Command.class); @@ -407,20 +405,14 @@ } public static final class Config extends ConfigObject { - private static final UUID ZERO_UUID = new UUID(0, 0); - - // Instance @LauncherAPI public final int port; private final StringConfigEntry address; private final String bindAddress; - // Skin system - @LauncherAPI public final String skinsURL; - @LauncherAPI public final String cloaksURL; - - // Auth + // Handlers & Providers @LauncherAPI public final AuthHandler authHandler; @LauncherAPI public final AuthProvider authProvider; + @LauncherAPI public final TextureProvider textureProvider; // EXE binary building @LauncherAPI public final boolean launch4J; @@ -433,23 +425,13 @@ bindAddress = block.hasEntry("bindAddress") ? block.getEntryValue("bindAddress", StringConfigEntry.class) : getAddress(); - // Skin system - skinsURL = block.getEntryValue("skinsURL", StringConfigEntry.class); - String skinURL = getTextureURL(skinsURL, "skinUsername", ZERO_UUID); - if (skinURL != null) { - IOHelper.verifyURL(skinURL); - } - cloaksURL = block.getEntryValue("cloaksURL", StringConfigEntry.class); - String cloakURL = getTextureURL(cloaksURL, "cloakUsername", ZERO_UUID); - if (cloakURL != null) { - IOHelper.verifyURL(cloakURL); - } - - // Set auth handler and provider - String authHandlerName = block.getEntryValue("authHandler", StringConfigEntry.class); - authHandler = AuthHandler.newHandler(authHandlerName, block.getEntry("authHandlerConfig", BlockConfigEntry.class)); - String authProviderName = block.getEntryValue("authProvider", StringConfigEntry.class); - authProvider = AuthProvider.newProvider(authProviderName, block.getEntry("authProviderConfig", BlockConfigEntry.class)); + // Set handlers & providers + authHandler = AuthHandler.newHandler(block.getEntryValue("authHandler", StringConfigEntry.class), + block.getEntry("authHandlerConfig", BlockConfigEntry.class)); + authProvider = AuthProvider.newProvider(block.getEntryValue("authProvider", StringConfigEntry.class), + block.getEntry("authProviderConfig", BlockConfigEntry.class)); + textureProvider = TextureProvider.newProvider(block.getEntryValue("textureProvider", StringConfigEntry.class), + block.getEntry("textureProviderConfig", BlockConfigEntry.class)); // Set launch4J config launch4J = block.getEntryValue("launch4J", BooleanConfigEntry.class); @@ -466,18 +448,6 @@ } @LauncherAPI - public PlayerProfile.Texture getCloak(String username, UUID uuid) { - String url = getTextureURL(cloaksURL, username, uuid); - return url == null ? null : getTexture(url); - } - - @LauncherAPI - public PlayerProfile.Texture getSkin(String username, UUID uuid) { - String url = getTextureURL(skinsURL, username, uuid); - return url == null ? null : getTexture(url); - } - - @LauncherAPI public SocketAddress getSocketAddress() { return new InetSocketAddress(bindAddress, port); } @@ -491,28 +461,5 @@ public void verify() { VerifyHelper.verify(getAddress(), VerifyHelper.NOT_EMPTY, "LaunchServer address can't be empty"); } - - @LauncherAPI - public static PlayerProfile.Texture getTexture(String url) { - LogHelper.debug("Getting texture: '%s'", url); - try { - return new PlayerProfile.Texture(url); - } catch (FileNotFoundException e) { - return null; // Simply not found - } catch (IOException e) { - LogHelper.error(new IOException(String.format("Can't digest texture: '%s'", url), e)); - return null; - } - } - - @LauncherAPI - public static String getTextureURL(String url, String username, UUID uuid) { - if (url.isEmpty()) { - return null; - } - return CommonHelper.replace(url, "username", IOHelper.urlEncode(username), - "uuid", IOHelper.urlEncode(uuid.toString()), - "hash", IOHelper.urlEncode(ClientLauncher.toHash(uuid))); - } } } diff --git a/LaunchServer/source/auth/handler/CachedAuthHandler.java b/LaunchServer/source/auth/handler/CachedAuthHandler.java index 2f5ffba..203b9ba 100644 --- a/LaunchServer/source/auth/handler/CachedAuthHandler.java +++ b/LaunchServer/source/auth/handler/CachedAuthHandler.java @@ -70,7 +70,7 @@ @LauncherAPI protected void addEntry(Entry entry) { Entry previous = entryCache.put(entry.uuid, entry); - if(previous != null) { // In case of username changing + if (previous != null) { // In case of username changing usernamesCache.remove(CommonHelper.low(previous.username)); } usernamesCache.put(CommonHelper.low(entry.username), entry.uuid); diff --git a/LaunchServer/source/auth/handler/MemoryAuthHandler.java b/LaunchServer/source/auth/handler/MemoryAuthHandler.java index c15b43a..3d24841 100644 --- a/LaunchServer/source/auth/handler/MemoryAuthHandler.java +++ b/LaunchServer/source/auth/handler/MemoryAuthHandler.java @@ -53,7 +53,7 @@ // Find username end int length = 0; - while(length < bytes.length && bytes[length] != 0) { + while (length < bytes.length && bytes[length] != 0) { length++; } diff --git a/LaunchServer/source/auth/provider/AuthProvider.java b/LaunchServer/source/auth/provider/AuthProvider.java index 30f80db..9ef0538 100644 --- a/LaunchServer/source/auth/provider/AuthProvider.java +++ b/LaunchServer/source/auth/provider/AuthProvider.java @@ -49,6 +49,6 @@ // Auth providers that doesn't do nothing :D registerProvider("mysql", MySQLAuthProvider::new); registerProvider("file", FileAuthProvider::new); - registerProvider("url", URLAuthProvider::new); + registerProvider("url", RequestAuthProvider::new); } } diff --git a/LaunchServer/source/auth/provider/RequestAuthProvider.java b/LaunchServer/source/auth/provider/RequestAuthProvider.java new file mode 100644 index 0000000..66726e5 --- /dev/null +++ b/LaunchServer/source/auth/provider/RequestAuthProvider.java @@ -0,0 +1,44 @@ +package launchserver.auth.provider; + +import java.io.IOException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import launcher.helper.CommonHelper; +import launcher.helper.IOHelper; +import launcher.serialize.config.entry.BlockConfigEntry; +import launcher.serialize.config.entry.StringConfigEntry; + +public final class RequestAuthProvider extends AuthProvider { + private final String url; + private final Pattern response; + + public RequestAuthProvider(BlockConfigEntry block) { + super(block); + url = block.getEntryValue("url", StringConfigEntry.class); + response = Pattern.compile(block.getEntryValue("response", StringConfigEntry.class)); + + // Verify is valid URL + IOHelper.verifyURL(getFormattedURL("urlAuthLogin", "urlAuthPassword")); + } + + @Override + public String auth(String login, String password) throws IOException { + String currentResponse = IOHelper.request(new URL(getFormattedURL(login, password))); + + // Match username + Matcher matcher = response.matcher(currentResponse); + return matcher.matches() && matcher.groupCount() >= 1 ? + matcher.group("username") : authError(currentResponse); + } + + @Override + public void flush() { + // Do nothing + } + + private String getFormattedURL(String login, String password) { + return CommonHelper.replace(url, "login", IOHelper.urlEncode(login), "password", IOHelper.urlEncode(password)); + } +} diff --git a/LaunchServer/source/auth/provider/URLAuthProvider.java b/LaunchServer/source/auth/provider/URLAuthProvider.java deleted file mode 100644 index 8cbcba2..0000000 --- a/LaunchServer/source/auth/provider/URLAuthProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package launchserver.auth.provider; - -import java.io.IOException; -import java.net.URL; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.serialize.config.entry.BlockConfigEntry; -import launcher.serialize.config.entry.StringConfigEntry; - -public final class URLAuthProvider extends AuthProvider { - private final String url; - private final Pattern response; - - public URLAuthProvider(BlockConfigEntry block) { - super(block); - url = block.getEntryValue("url", StringConfigEntry.class); - response = Pattern.compile(block.getEntryValue("response", StringConfigEntry.class)); - - // Verify is valid URL - IOHelper.verifyURL(getFormattedURL("urlAuthLogin", "urlAuthPassword")); - } - - @Override - public String auth(String login, String password) throws IOException { - String currentResponse = IOHelper.request(new URL(getFormattedURL(login, password))); - - // Match username - Matcher matcher = response.matcher(currentResponse); - return matcher.matches() && matcher.groupCount() >= 1 ? - matcher.group("username") : authError(currentResponse); - } - - @Override - public void flush() { - // Do nothing - } - - private String getFormattedURL(String login, String password) { - return CommonHelper.replace(url, "login", IOHelper.urlEncode(login), "password", IOHelper.urlEncode(password)); - } -} diff --git a/LaunchServer/source/response/auth/AuthResponse.java b/LaunchServer/source/response/auth/AuthResponse.java index 38c6596..0648992 100644 --- a/LaunchServer/source/response/auth/AuthResponse.java +++ b/LaunchServer/source/response/auth/AuthResponse.java @@ -44,6 +44,7 @@ username = server.config.authProvider.auth(login, password); if (!VerifyHelper.isValidUsername(username)) { AuthProvider.authError(String.format("Illegal username: '%s'", username)); + return; } } catch (AuthException e) { requestError(e.getMessage()); @@ -59,7 +60,7 @@ String accessToken = SecurityHelper.randomStringToken(); UUID uuid; try { - uuid =server.config.authHandler.auth(username, accessToken); + uuid = server.config.authHandler.auth(username, accessToken); } catch (AuthException e) { requestError(e.getMessage()); return; diff --git a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java index 8a4864f..77b97c1 100644 --- a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java @@ -4,6 +4,7 @@ import java.util.UUID; import launcher.client.PlayerProfile; +import launcher.helper.LogHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launchserver.LaunchServer; @@ -32,8 +33,25 @@ } public static PlayerProfile getProfile(LaunchServer server, UUID uuid, String username) { - PlayerProfile.Texture skinURL = server.config.getSkin(username, uuid); - PlayerProfile.Texture cloakURL = server.config.getCloak(username, uuid); - return new PlayerProfile(uuid, username, skinURL, cloakURL); + // Get skin texture + PlayerProfile.Texture skin; + try { + skin = server.config.textureProvider.getSkinTexture(uuid, username); + } catch (IOException e) { + LogHelper.error(new IOException(String.format("Can't get skin texture: '%s'", username), e)); + skin = null; + } + + // Get cloak texture + PlayerProfile.Texture cloak; + try { + cloak = server.config.textureProvider.getCloakTexture(uuid, username); + } catch (IOException e) { + LogHelper.error(new IOException(String.format("Can't get cloak texture: '%s'", username), e)); + cloak = null; + } + + // Return combined profile + return new PlayerProfile(uuid, username, skin, cloak); } } diff --git a/LaunchServer/source/texture/NullTextureProvider.java b/LaunchServer/source/texture/NullTextureProvider.java new file mode 100644 index 0000000..fed0be0 --- /dev/null +++ b/LaunchServer/source/texture/NullTextureProvider.java @@ -0,0 +1,44 @@ +package launchserver.texture; + +import java.io.IOException; +import java.util.UUID; + +import launcher.LauncherAPI; +import launcher.client.PlayerProfile; +import launcher.helper.VerifyHelper; +import launcher.serialize.config.entry.BlockConfigEntry; + +public final class NullTextureProvider extends TextureProvider { + private volatile TextureProvider provider; + + public NullTextureProvider(BlockConfigEntry block) { + super(block); + } + + @Override + public void flush() throws IOException { + TextureProvider provider = this.provider; + if (provider != null) { + provider.flush(); + } + } + + @Override + public PlayerProfile.Texture getCloakTexture(UUID uuid, String username) throws IOException { + return getProvider().getCloakTexture(uuid, username); + } + + @Override + public PlayerProfile.Texture getSkinTexture(UUID uuid, String username) throws IOException { + return getProvider().getSkinTexture(uuid, username); + } + + @LauncherAPI + public void setBackend(TextureProvider provider) { + this.provider = provider; + } + + private TextureProvider getProvider() { + return VerifyHelper.verify(provider, a -> a != null, "Backend texture provider wasn't set"); + } +} diff --git a/LaunchServer/source/texture/RequestTextureProvider.java b/LaunchServer/source/texture/RequestTextureProvider.java new file mode 100644 index 0000000..a5aca61 --- /dev/null +++ b/LaunchServer/source/texture/RequestTextureProvider.java @@ -0,0 +1,60 @@ +package launchserver.texture; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.UUID; + +import launcher.client.ClientLauncher; +import launcher.client.PlayerProfile; +import launcher.helper.CommonHelper; +import launcher.helper.IOHelper; +import launcher.helper.LogHelper; +import launcher.serialize.config.entry.BlockConfigEntry; +import launcher.serialize.config.entry.StringConfigEntry; + +public final class RequestTextureProvider extends TextureProvider { + private static final UUID ZERO_UUID = new UUID(0, 0); + + // Instance + private final String skinURL; + private final String cloakURL; + + public RequestTextureProvider(BlockConfigEntry block) { + super(block); + skinURL = block.getEntryValue("skinsURL", StringConfigEntry.class); + cloakURL = block.getEntryValue("cloaksURL", StringConfigEntry.class); + + // Verify + IOHelper.verifyURL(getTextureURL(skinURL, ZERO_UUID, "skinUsername")); + IOHelper.verifyURL(getTextureURL(cloakURL, ZERO_UUID, "cloakUsername")); + } + + @Override + public void flush() throws IOException { + // Do nothing + } + + @Override + public PlayerProfile.Texture getCloakTexture(UUID uuid, String username) throws IOException { + return getTexture(getTextureURL(cloakURL, uuid, username)); + } + + @Override + public PlayerProfile.Texture getSkinTexture(UUID uuid, String username) throws IOException { + return getTexture(getTextureURL(cloakURL, uuid, username)); + } + + private static PlayerProfile.Texture getTexture(String url) throws IOException { + LogHelper.debug("Getting texture: '%s'", url); + try { + return new PlayerProfile.Texture(url); + } catch (FileNotFoundException e) { + return null; // Simply not found + } + } + + private static String getTextureURL(String url, UUID uuid, String username) { + return CommonHelper.replace(url, "username", IOHelper.urlEncode(username), + "uuid", IOHelper.urlEncode(uuid.toString()), "hash", IOHelper.urlEncode(ClientLauncher.toHash(uuid))); + } +} diff --git a/LaunchServer/source/texture/TextureProvider.java b/LaunchServer/source/texture/TextureProvider.java new file mode 100644 index 0000000..0c88fd8 --- /dev/null +++ b/LaunchServer/source/texture/TextureProvider.java @@ -0,0 +1,51 @@ +package launchserver.texture; + +import java.io.Flushable; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import launcher.LauncherAPI; +import launcher.client.PlayerProfile; +import launcher.helper.VerifyHelper; +import launcher.serialize.config.ConfigObject; +import launcher.serialize.config.entry.BlockConfigEntry; + +public abstract class TextureProvider extends ConfigObject implements Flushable { + private static final Map> TEXTURE_PROVIDERS = new ConcurrentHashMap<>(2); + + @LauncherAPI + protected TextureProvider(BlockConfigEntry block) { + super(block); + } + + @LauncherAPI + public abstract PlayerProfile.Texture getCloakTexture(UUID uuid, String username) throws IOException; + + @LauncherAPI + public abstract PlayerProfile.Texture getSkinTexture(UUID uuid, String username) throws IOException; + + @LauncherAPI + public static TextureProvider newProvider(String name, BlockConfigEntry block) { + VerifyHelper.verifyIDName(name); + Adapter authHandlerAdapter = VerifyHelper.getMapValue(TEXTURE_PROVIDERS, name, + String.format("Unknown texture provider: '%s'", name)); + return authHandlerAdapter.convert(block); + } + + @LauncherAPI + public static void registerProvider(String name, Adapter adapter) { + VerifyHelper.putIfAbsent(TEXTURE_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Texture provider has been already registered: '%s'", name)); + } + + static { + registerProvider("null", NullTextureProvider::new); + registerProvider("void", VoidTextureProvider::new); + + // Auth providers that doesn't do nothing :D + registerProvider("request", RequestTextureProvider::new); + } +} diff --git a/LaunchServer/source/texture/VoidTextureProvider.java b/LaunchServer/source/texture/VoidTextureProvider.java new file mode 100644 index 0000000..b681fdb --- /dev/null +++ b/LaunchServer/source/texture/VoidTextureProvider.java @@ -0,0 +1,28 @@ +package launchserver.texture; + +import java.io.IOException; +import java.util.UUID; + +import launcher.client.PlayerProfile; +import launcher.serialize.config.entry.BlockConfigEntry; + +public final class VoidTextureProvider extends TextureProvider { + public VoidTextureProvider(BlockConfigEntry block) { + super(block); + } + + @Override + public void flush() throws IOException { + // Do nothing + } + + @Override + public PlayerProfile.Texture getCloakTexture(UUID uuid, String username) { + return null; // Always nothing + } + + @Override + public PlayerProfile.Texture getSkinTexture(UUID uuid, String username) { + return null; // Always nothing + } +} diff --git a/Launcher/runtime/engine/api.js b/Launcher/runtime/engine/api.js index a42c7ea..a76cfde 100755 --- a/Launcher/runtime/engine/api.js +++ b/Launcher/runtime/engine/api.js @@ -2,6 +2,7 @@ // Hasher class API imports var PlayerProfile = PlayerProfileClass.static; +var PlayerProfileTexture = PlayerProfileTextureClass.static; var ClientProfile = ClientProfileClass.static; var ClientProfileVersion = ClientProfileVersionClass.static; var ClientLauncher = ClientLauncherClass.static; @@ -63,7 +64,7 @@ // Helper JS class API imports var JSApplication = null; -if(typeof JSApplicationClass !== 'undefined') { +if (typeof JSApplicationClass !== 'undefined') { JSApplication = JSApplicationClass.static; } @@ -77,11 +78,18 @@ } function newTask(r) { - return new javafx.concurrent.Task() { call: r }; + return new javafx.concurrent.Task() + { + call: r + } + ; } function newRequestTask(request) { - return newTask(function() request.request()); + return newTask(function () + request.request() +) + ; } function startTask(task) { diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 42e2671..732e3f5 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -123,6 +123,7 @@ // Set client class bindings bindings.put("PlayerProfileClass", PlayerProfile.class); + bindings.put("PlayerProfileTextureClass", PlayerProfile.Texture.class); bindings.put("ClientProfileClass", ClientProfile.class); bindings.put("ClientProfileVersionClass", ClientProfile.Version.class); bindings.put("ClientLauncherClass", ClientLauncher.class);