diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index 768fd75..a45035f 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -28,6 +28,7 @@ import java.util.LinkedList; 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; @@ -116,13 +117,13 @@ reloadConfig(); syncLauncherBinaries(); - // Hash updates dir + // Sync updates dir if (!IOHelper.isDir(UPDATES_DIR)) { Files.createDirectory(UPDATES_DIR); } syncUpdatesDir(null); - // Hash profiles dir + // Sync profiles dir if (!IOHelper.isDir(PROFILES_DIR)) { Files.createDirectory(PROFILES_DIR); } @@ -193,66 +194,8 @@ } @LauncherAPI - public void syncLauncherBinaries() throws IOException { - LogHelper.info("Syncing launcher binaries"); - - // Syncing launcher binary - LogHelper.subInfo("Syncing launcher binary file"); - if (!launcherBinary.sync()) { - LogHelper.subWarning("Missing launcher binary file"); - } - - // Syncing launcher EXE binary - LogHelper.subInfo("Syncing launcher EXE binary file"); - if (!launcherEXEBinary.sync()) { - LogHelper.subWarning("Missing launcher EXE binary file"); - } - } - - @LauncherAPI - public void syncProfilesDir() throws IOException { - LogHelper.info("Syncing profiles dir"); - List> newProfies = new LinkedList<>(); - IOHelper.walk(PROFILES_DIR, new ProfilesFileVisitor(newProfies), false); - - // Sort and set new profiles - Collections.sort(newProfies, (a, b) -> a.object.compareTo(b.object)); - profilesList = Collections.unmodifiableList(newProfies); - } - - @LauncherAPI - public void syncUpdatesDir(Collection dirs) throws IOException { - LogHelper.info("Syncing updates dir"); - Map> newUpdatesDirMap = new HashMap<>(16); - try (DirectoryStream dirStream = Files.newDirectoryStream(UPDATES_DIR)) { - for (Path updateDir : dirStream) { - if (Files.isHidden(updateDir)) { - continue; // Skip hidden - } - - // Resolve name and verify is dir - String name = IOHelper.getFileName(updateDir); - if (!IOHelper.isDir(updateDir)) { - LogHelper.subWarning("Not update dir: '%s'", name); - continue; - } - - // Add from previous map (it's guaranteed to be non-null) - if (dirs != null && !dirs.contains(name)) { - SignedObjectHolder hdir = updatesDirMap.get(name); - if (hdir != null) { - newUpdatesDirMap.put(name, hdir); - continue; - } - } - - // Hash and sign update dir - LogHelper.subInfo("Syncing '%s' update dir", name); - HashedDir updateHDir = new HashedDir(updateDir, null); - newUpdatesDirMap.put(name, new SignedObjectHolder<>(updateHDir, privateKey)); - } - } - updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); + public Set>> getUpdateDirs() { + return updatesDirMap.entrySet(); } @LauncherAPI @@ -362,6 +305,69 @@ privateKey = newPrivateKey; } + @LauncherAPI + public void syncLauncherBinaries() throws IOException { + LogHelper.info("Syncing launcher binaries"); + + // Syncing launcher binary + LogHelper.subInfo("Syncing launcher binary file"); + if (!launcherBinary.sync()) { + LogHelper.subWarning("Missing launcher binary file"); + } + + // Syncing launcher EXE binary + LogHelper.subInfo("Syncing launcher EXE binary file"); + if (!launcherEXEBinary.sync()) { + LogHelper.subWarning("Missing launcher EXE binary file"); + } + } + + @LauncherAPI + public void syncProfilesDir() throws IOException { + LogHelper.info("Syncing profiles dir"); + List> newProfies = new LinkedList<>(); + IOHelper.walk(PROFILES_DIR, new ProfilesFileVisitor(newProfies), false); + + // Sort and set new profiles + Collections.sort(newProfies, (a, b) -> a.object.compareTo(b.object)); + profilesList = Collections.unmodifiableList(newProfies); + } + + @LauncherAPI + public void syncUpdatesDir(Collection dirs) throws IOException { + LogHelper.info("Syncing updates dir"); + Map> newUpdatesDirMap = new HashMap<>(16); + try (DirectoryStream dirStream = Files.newDirectoryStream(UPDATES_DIR)) { + for (Path updateDir : dirStream) { + if (Files.isHidden(updateDir)) { + continue; // Skip hidden + } + + // Resolve name and verify is dir + String name = IOHelper.getFileName(updateDir); + if (!IOHelper.isDir(updateDir)) { + LogHelper.subWarning("Not update dir: '%s'", name); + continue; + } + + // Add from previous map (it's guaranteed to be non-null) + if (dirs != null && !dirs.contains(name)) { + SignedObjectHolder hdir = updatesDirMap.get(name); + if (hdir != null) { + newUpdatesDirMap.put(name, hdir); + continue; + } + } + + // Sync and sign update dir + LogHelper.subInfo("Syncing '%s' update dir", name); + HashedDir updateHDir = new HashedDir(updateDir, null); + newUpdatesDirMap.put(name, new SignedObjectHolder<>(updateHDir, privateKey)); + } + } + updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); + } + private void setScriptBindings() { LogHelper.info("Setting up server script engine bindings"); Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); @@ -439,7 +445,7 @@ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - LogHelper.subInfo("Hashing '%s' profile", IOHelper.getFileName(file)); + LogHelper.subInfo("Sync '%s' profile", IOHelper.getFileName(file)); // Read profile ClientProfile profile; diff --git a/LaunchServer/source/auth/handler/AuthHandler.java b/LaunchServer/source/auth/handler/AuthHandler.java index 1b49052..13bb445 100644 --- a/LaunchServer/source/auth/handler/AuthHandler.java +++ b/LaunchServer/source/auth/handler/AuthHandler.java @@ -45,8 +45,8 @@ @LauncherAPI public static void registerHandler(String name, Adapter adapter) { VerifyHelper.verifyIDName(name); - VerifyHelper.verify(AUTH_HANDLERS.putIfAbsent(name, Objects.requireNonNull(adapter, "adapter")), - a -> a == null, String.format("Auth handler has been already registered: '%s'", name)); + VerifyHelper.putIfAbsent(AUTH_HANDLERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Auth handler has been already registered: '%s'", name)); } static { diff --git a/LaunchServer/source/auth/provider/AuthProvider.java b/LaunchServer/source/auth/provider/AuthProvider.java index 5e580ab..c8eee41 100644 --- a/LaunchServer/source/auth/provider/AuthProvider.java +++ b/LaunchServer/source/auth/provider/AuthProvider.java @@ -31,8 +31,8 @@ @LauncherAPI public static void registerProvider(String name, Adapter adapter) { - VerifyHelper.verify(AUTH_PROVIDERS.putIfAbsent(name, Objects.requireNonNull(adapter, "adapter")), - a -> a == null, String.format("Auth provider has been already registered: '%s'", name)); + VerifyHelper.putIfAbsent(AUTH_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Auth provider has been already registered: '%s'", name)); } static { diff --git a/LaunchServer/source/command/handler/CommandHandler.java b/LaunchServer/source/command/handler/CommandHandler.java index 808fdc1..a0888f4 100644 --- a/LaunchServer/source/command/handler/CommandHandler.java +++ b/LaunchServer/source/command/handler/CommandHandler.java @@ -134,8 +134,8 @@ @LauncherAPI public final void register(String name, Command command) { VerifyHelper.verifyIDName(name); - VerifyHelper.verify(commands.putIfAbsent(name, Objects.requireNonNull(command, "command")), - c -> c == null, String.format("Command has been already registered: '%s'", name)); + VerifyHelper.putIfAbsent(commands, name, Objects.requireNonNull(command, "command"), + String.format("Command has been already registered: '%s'", name)); } private void readLoop() throws IOException { diff --git a/LaunchServer/source/response/ResponseThread.java b/LaunchServer/source/response/ResponseThread.java index d2d75b0..5f6d9a5 100644 --- a/LaunchServer/source/response/ResponseThread.java +++ b/LaunchServer/source/response/ResponseThread.java @@ -24,6 +24,7 @@ import launchserver.response.profile.ProfileByUUIDResponse; import launchserver.response.profile.ProfileByUsernameResponse; import launchserver.response.update.LauncherResponse; +import launchserver.response.update.UpdateListResponse; import launchserver.response.update.UpdateResponse; public final class ResponseThread implements Runnable { @@ -107,6 +108,9 @@ case UPDATE: response = new UpdateResponse(server, input, output); break; + case UPDATE_LIST: + response = new UpdateListResponse(server, input, output); + break; case PROFILE_BY_USERNAME: response = new ProfileByUsernameResponse(server, input, output); break; diff --git a/LaunchServer/source/response/ServerSocketHandler.java b/LaunchServer/source/response/ServerSocketHandler.java index 5448b9c..8504e29 100644 --- a/LaunchServer/source/response/ServerSocketHandler.java +++ b/LaunchServer/source/response/ServerSocketHandler.java @@ -98,8 +98,8 @@ @LauncherAPI public void registerCustomResponse(String name, Response.Factory factory) { VerifyHelper.verifyIDName(name); - VerifyHelper.verify(customResponses.putIfAbsent(name, Objects.requireNonNull(factory, "factory")), - c -> c == null, String.format("Custom response has been already registered: '%s'", name)); + VerifyHelper.putIfAbsent(customResponses, name, Objects.requireNonNull(factory, "factory"), + String.format("Custom response has been already registered: '%s'", name)); } @LauncherAPI diff --git a/LaunchServer/source/response/update/UpdateListResponse.java b/LaunchServer/source/response/update/UpdateListResponse.java new file mode 100644 index 0000000..28e252e --- /dev/null +++ b/LaunchServer/source/response/update/UpdateListResponse.java @@ -0,0 +1,28 @@ +package launchserver.response.update; + +import java.util.Map; +import java.util.Set; + +import launcher.hasher.HashedDir; +import launcher.serialize.HInput; +import launcher.serialize.HOutput; +import launcher.serialize.signed.SignedObjectHolder; +import launchserver.LaunchServer; +import launchserver.response.Response; + +public final class UpdateListResponse extends Response { + public UpdateListResponse(LaunchServer server, HInput input, HOutput output) { + super(server, input, output); + } + + @Override + public void reply() throws Exception { + Set>> updateDirs = server.getUpdateDirs(); + + // Write all update dirs names + output.writeLength(updateDirs.size(), 0); + for (Map.Entry> entry : updateDirs) { + output.writeString(entry.getKey(), 255); + } + } +} diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 3e67c8d..da1b3f8 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -279,7 +279,10 @@ int count = input.readLength(0); Map localResources = new HashMap<>(count); for (int i = 0; i < count; i++) { - localResources.put(input.readString(255), input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH)); + String name = input.readString(255); + VerifyHelper.putIfAbsent(localResources, name, + input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH), + String.format("Duplicate runtime resource: '%s'", name)); } runtime = Collections.unmodifiableMap(localResources); diff --git a/Launcher/source/helper/VerifyHelper.java b/Launcher/source/helper/VerifyHelper.java index beee3f6..5cb9a49 100644 --- a/Launcher/source/helper/VerifyHelper.java +++ b/Launcher/source/helper/VerifyHelper.java @@ -22,7 +22,7 @@ @LauncherAPI public static V getMapValue(Map map, K key, String error) { - return verify(map.get(key), v -> v != null, error); + return verify(map.get(key), v -> v == null, error); } @LauncherAPI @@ -40,6 +40,10 @@ return USERNAME_PATTERN.matcher(username).matches(); } + public static void putIfAbsent(Map map, K key, V value, String error) { + verify(map.putIfAbsent(key, value), o -> o != null, error); + } + @LauncherAPI public static IntPredicate range(int min, int max) { return i -> i >= min && i <= max; diff --git a/Launcher/source/request/update/UpdateListRequest.java b/Launcher/source/request/update/UpdateListRequest.java index 0ecd44f..eabc16a 100644 --- a/Launcher/source/request/update/UpdateListRequest.java +++ b/Launcher/source/request/update/UpdateListRequest.java @@ -31,6 +31,8 @@ @Override protected Set requestDo(HInput input, HOutput output) throws IOException { int count = input.readLength(0); + + // Read all update dirs names Set result = new HashSet<>(count); for (int i = 0; i < count; i++) { result.add(IOHelper.verifyFileName(input.readString(255)));