From 88a70bf47bff45b8751dcd961329d58be5df582a Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:09:38 +0700 Subject: [PATCH] [FEATURE][EXPERIMENTAL] Refactor ProfilesProvider --- .../pro/gravit/launchserver/LaunchServer.java | 64 --- .../launchserver/LaunchServerStarter.java | 4 +- .../auth/profiles/LocalProfileProvider.java | 108 ----- .../auth/profiles/LocalProfilesProvider.java | 427 ++++++++++++++++++ .../auth/profiles/ProfileProvider.java | 76 ---- .../auth/profiles/ProfilesProvider.java | 87 ++++ .../auth/protect/StdProtectHandler.java | 41 +- .../interfaces/ProfilesProtectHandler.java | 22 - .../auth/updates/LocalUpdatesProvider.java | 247 +++------- .../auth/updates/UpdatesProvider.java | 95 +--- .../binary/EXELauncherBinary.java | 11 +- .../binary/JARLauncherBinary.java | 8 +- .../launchserver/binary/LauncherBinary.java | 31 +- .../gravit/launchserver/command/Command.java | 2 +- .../command/basic/BuildCommand.java | 1 - .../command/handler/CommandHandler.java | 6 +- .../command/hash/DownloadAssetCommand.java | 137 ------ .../command/hash/DownloadClientCommand.java | 108 ----- .../command/hash/IndexAssetCommand.java | 4 +- .../command/hash/UnindexAssetCommand.java | 4 +- .../command/profiles/CloneProfileCommand.java | 21 +- .../profiles/CreateProfileCommand.java | 140 ++++++ .../profiles/DeleteProfileCommand.java | 17 +- .../command/profiles/ListProfilesCommand.java | 4 +- .../command/profiles/MakeProfileCommand.java | 42 -- .../command/profiles/ProfilesCommand.java | 3 +- .../command/profiles/SaveProfilesCommand.java | 46 -- .../command/service/SecurityCheckCommand.java | 6 +- .../command/service/TokenCommand.java | 9 +- .../command/sync/SyncBinariesCommand.java | 32 -- .../command/sync/SyncCommand.java | 5 - .../command/sync/SyncProfilesCommand.java | 32 -- .../command/sync/SyncUPCommand.java | 35 -- .../command/sync/SyncUpdatesCacheCommand.java | 25 - .../command/sync/SyncUpdatesCommand.java | 42 -- .../config/LaunchServerConfig.java | 21 +- .../launchserver/helper/AssetsDirHelper.java | 67 +++ .../launchserver/manangers/AuthManager.java | 2 +- .../manangers/LaunchServerGsonManager.java | 4 +- .../manangers/UpdatesManager.java | 45 -- .../gravit/launchserver/socket/Client.java | 3 +- .../response/auth/ProfilesResponse.java | 34 +- .../response/auth/SetProfileResponse.java | 26 +- .../response/update/LauncherResponse.java | 38 +- .../response/update/UpdateResponse.java | 18 +- .../request/SetProfileRequestEvent.java | 8 + .../base/request/auth/SetProfileRequest.java | 13 +- modules | 2 +- 48 files changed, 925 insertions(+), 1298 deletions(-) delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfileProvider.java create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfilesProvider.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfileProvider.java create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfilesProvider.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/interfaces/ProfilesProtectHandler.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadAssetCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadClientCommand.java create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CreateProfileCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/MakeProfileCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/SaveProfilesCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncBinariesCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncProfilesCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUPCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCacheCommand.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCommand.java create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/helper/AssetsDirHelper.java delete mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/manangers/UpdatesManager.java diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java index 417fe442..676ff122 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java @@ -106,7 +106,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab public final ConfigManager configManager; public final FeaturesManager featuresManager; public final KeyAgreementManager keyAgreementManager; - public final UpdatesManager updatesManager; // HWID ban + anti-brutforce public final CertificateManager certificateManager; // Server @@ -162,7 +161,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La configManager = new ConfigManager(); featuresManager = new FeaturesManager(this); authManager = new AuthManager(this); - updatesManager = new UpdatesManager(this); RestoreResponse.registerProviders(this); config.init(ReloadType.FULL); @@ -179,7 +177,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La launcherBinary.init(); launcherEXEBinary.init(); - syncLauncherBinaries(); launcherModuleLoader = new LauncherModuleLoader(this); if (config.components != null) { logger.debug("Init components"); @@ -339,11 +336,6 @@ public void close() throws Exception { logger.info("LaunchServer stopped"); } - @Deprecated - public Set getProfiles() { - return config.profileProvider.getProfiles(); - } - @Deprecated public void setProfiles(Set profilesList) { throw new UnsupportedOperationException(); @@ -370,21 +362,6 @@ public void run() { })); CommonHelper.newThread("Command Thread", true, commandHandler).start(); CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start(); - // Sync updates dir - CommonHelper.newThread("Profiles and updates sync", true, () -> { - try { - // Sync profiles dir - syncProfilesDir(); - - // Sync updates dir - config.updatesProvider.syncInitially(); - - - modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this)); - } catch (IOException e) { - logger.error("Updates/Profiles not synced", e); - } - }).start(); } if (config.netty != null) rebindNettyServerSocket(); @@ -398,47 +375,6 @@ public void run() { } } - public void syncLauncherBinaries() throws IOException { - logger.info("Syncing launcher binaries"); - - // Syncing launcher binary - logger.info("Syncing launcher binary file"); - if (!launcherBinary.sync()) logger.warn("Missing launcher binary file"); - - // Syncing launcher EXE binary - logger.info("Syncing launcher EXE binary file"); - if (!launcherEXEBinary.sync()) - logger.warn("Missing launcher EXE binary file"); - - } - - public void syncProfilesDir() throws IOException { - logger.info("Syncing profiles dir"); - config.profileProvider.sync(); - if (config.netty.sendProfileUpdatesEvent) { - sendUpdateProfilesEvent(); - } - } - - private void sendUpdateProfilesEvent() { - if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) { - return; - } - nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> { - Client client = handler.getClient(); - if (client == null || !client.isAuth) { - return; - } - ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client)); - event.requestUUID = RequestEvent.eventUUID; - handler.service.sendObject(ch, event); - }); - } - - public void syncUpdatesDir(Collection dirs) throws IOException { - updatesManager.syncUpdatesDir(dirs); - } - public void registerObject(String name, Object object) { if (object instanceof Reconfigurable) { reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java index aec1c73e..99eb94f0 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java @@ -13,7 +13,7 @@ import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.password.PasswordVerifier; -import pro.gravit.launchserver.auth.profiles.ProfileProvider; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.updates.UpdatesProvider; @@ -179,7 +179,7 @@ public static void registerAll() { OptionalAction.registerProviders(); OptionalTrigger.registerProviders(); MixProvider.registerProviders(); - ProfileProvider.registerProviders(); + ProfilesProvider.registerProviders(); UpdatesProvider.registerProviders(); } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfileProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfileProvider.java deleted file mode 100644 index c938a1f8..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfileProvider.java +++ /dev/null @@ -1,108 +0,0 @@ -package pro.gravit.launchserver.auth.profiles; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.Launcher; -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.utils.helper.IOHelper; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; - -public class LocalProfileProvider extends ProfileProvider { - public String profilesDir = "profiles"; - private transient volatile Map profilesMap; - private transient volatile Set profilesList; // Cache - @Override - public void sync() throws IOException { - Path profilesDirPath = Path.of(profilesDir); - if (!IOHelper.isDir(profilesDirPath)) - Files.createDirectory(profilesDirPath); - Map newProfiles = new HashMap<>(); - IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false); - Set newProfilesList = new HashSet<>(newProfiles.values()); - profilesMap = newProfiles; - profilesList = newProfilesList; - } - - @Override - public Set getProfiles() { - return profilesList; - } - - @Override - public void addProfile(ClientProfile profile) throws IOException { - Path profilesDirPath = Path.of(profilesDir); - ClientProfile oldProfile; - Path target = null; - for(var e : profilesMap.entrySet()) { - if(e.getValue().getUUID().equals(profile.getUUID())) { - target = e.getKey(); - } - } - if(target == null) { - target = profilesDirPath.resolve(profile.getTitle()+".json"); - oldProfile = profilesMap.get(target); - if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) { - throw new FileAlreadyExistsException(target.toString()); - } - } - try (BufferedWriter writer = IOHelper.newWriter(target)) { - Launcher.gsonManager.configGson.toJson(profile, writer); - } - addProfile(target, profile); - } - - @Override - public void deleteProfile(ClientProfile profile) throws IOException { - for(var e : profilesMap.entrySet()) { - if(e.getValue().getUUID().equals(profile.getUUID())) { - Files.deleteIfExists(e.getKey()); - profilesMap.remove(e.getKey()); - profilesList.remove(e.getValue()); - break; - } - } - } - - private void addProfile(Path path, ClientProfile profile) { - for(var e : profilesMap.entrySet()) { - if(e.getValue().getUUID().equals(profile.getUUID())) { - profilesMap.remove(e.getKey()); - profilesList.remove(e.getValue()); - break; - } - } - profilesMap.put(path, profile); - profilesList.add(profile); - } - - private static final class ProfilesFileVisitor extends SimpleFileVisitor { - private final Map result; - private final Logger logger = LogManager.getLogger(); - - private ProfilesFileVisitor(Map result) { - this.result = result; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - logger.info("Syncing '{}' profile", IOHelper.getFileName(file)); - - // Read profile - ClientProfile profile; - try (BufferedReader reader = IOHelper.newReader(file)) { - profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class); - } - profile.verify(); - - // Add SIGNED profile to result list - result.put(file.toAbsolutePath(), profile); - return super.visitFile(file, attrs); - } - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfilesProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfilesProvider.java new file mode 100644 index 00000000..9abd49bd --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/LocalProfilesProvider.java @@ -0,0 +1,427 @@ +package pro.gravit.launchserver.auth.profiles; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launcher.core.serialize.HInput; +import pro.gravit.launcher.core.serialize.HOutput; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.Reconfigurable; +import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.utils.command.Command; +import pro.gravit.utils.command.SubCommand; +import pro.gravit.utils.helper.IOHelper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.stream.Stream; + +public class LocalProfilesProvider extends ProfilesProvider implements Reconfigurable { + private final transient Logger logger = LogManager.getLogger(); + public String profilesDir = "profiles"; + public String cacheFile = ".updates-cache"; + public String updatesDir = "updates"; + public boolean cacheUpdates = true; + private transient volatile Map updatesDirMap; + private transient volatile Map profilesMap; + + @Override + public UncompletedProfile create(String name, String description, CompletedProfile reference) { + LocalProfile ref = (LocalProfile) reference; + LocalProfile profile; + if(ref != null) { + ClientProfile newClientProfile = new ClientProfileBuilder(ref.profile) + .setTitle(name) + .setInfo(description) + .setDir(name) + .createClientProfile(); + profile = new LocalProfile(newClientProfile, ref.clientDir, ref.assetDir); + Path updatesDirPath = Path.of(updatesDir); + try { + IOHelper.copy(updatesDirPath.resolve(ref.profile.getDir()), updatesDirPath.resolve(profile.profile.getDir())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + profile = new LocalProfile(new ClientProfileBuilder() + .setUuid(UUID.randomUUID()) + .setTitle(name) + .setInfo(description) + .setDir(name) + .setAssetDir("assets") + .createClientProfile(), null, getUpdatesDir("assets")); + } + profilesMap.put(profile.getUuid(), profile); + try(Writer writer = IOHelper.newWriter(profile.getConfigPath())) { + Launcher.gsonManager.configGson.toJson(profile.profile, writer); + } catch (IOException e) { + throw new RuntimeException(e); + } + return profile; + } + + @Override + public void delete(UncompletedProfile profile) { + LocalProfile p = (LocalProfile) profile; + profilesMap.remove(p.getUuid()); + try { + Path updatesDirPath = Path.of(updatesDir); + IOHelper.deleteDir(updatesDirPath.resolve(p.profile.getDir()), true); + Files.delete(p.getConfigPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Set getProfiles(Client client) { + return new HashSet<>(profilesMap.values()); + } + + @Override + public CompletedProfile pushUpdate(UncompletedProfile profile, String tag, ClientProfile clientProfile, List assetActions, List clientActions, List flags) throws IOException { + Path updatesDirPath = Path.of(updatesDir); + LocalProfile localProfile = (LocalProfile) profile; + localProfile = new LocalProfile(clientProfile, localProfile.clientDir, localProfile.assetDir); + localProfile.profile = clientProfile; + if(assetActions != null && !assetActions.isEmpty()) { + Path assetDir = updatesDirPath.resolve(clientProfile.getAssetDir()); + execute(localProfile.assetDir, assetDir, assetActions); + localProfile.assetDir = new HashedDir(assetDir, null, true, true); + } + if(clientActions != null && !clientActions.isEmpty()) { + Path clientDir = updatesDirPath.resolve(clientProfile.getDir()); + execute(localProfile.clientDir, clientDir, clientActions); + localProfile.clientDir = new HashedDir(clientDir, null, true, true); + } + profilesMap.put(localProfile.getUuid(), localProfile); + return localProfile; + } + + @Override + public void download(CompletedProfile profile, Map files, boolean assets) throws IOException { + Path sourceDir = Path.of(updatesDir).resolve(assets ? profile.getProfile().getAssetDir() : profile.getProfile().getDir()); + for(var e : files.entrySet()) { + var source = sourceDir.resolve(e.getKey()); + var target = e.getValue(); + IOHelper.createParentDirs(target); + IOHelper.copy(source, target); + } + } + + @Override + public HashedDir getUnconnectedDirectory(String name) { + return getUpdatesDir(name); + } + + @Override + public CompletedProfile get(UUID uuid, String tag) { + return profilesMap.get(uuid); + } + + @Override + public CompletedProfile get(String name, String tag) { + for(var p : profilesMap.values()) { + if(p.getName() != null && p.getName().equals(name)) { + return p; + } + } + return null; + } + + private void writeCache(Path file) throws IOException { + try (HOutput output = new HOutput(IOHelper.newOutput(file))) { + output.writeLength(updatesDirMap.size(), 0); + for (Map.Entry entry : updatesDirMap.entrySet()) { + output.writeString(entry.getKey(), 0); + entry.getValue().write(output); + } + } + logger.debug("Saved {} updates to cache", updatesDirMap.size()); + } + + private void readCache(Path file) throws IOException { + Map updatesDirMap = new HashMap<>(16); + try (HInput input = new HInput(IOHelper.newInput(file))) { + int size = input.readLength(0); + for (int i = 0; i < size; ++i) { + String name = input.readString(0); + HashedDir dir = new HashedDir(input); + updatesDirMap.put(name, dir); + } + } + logger.debug("Found {} updates from cache", updatesDirMap.size()); + this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap); + } + + public void readProfilesDir() throws IOException { + Path profilesDirPath = Path.of(profilesDir); + Map newProfiles = new HashMap<>(); + IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false); + profilesMap = newProfiles; + } + + public void readUpdatesDir() throws IOException { + var cacheFilePath = Path.of(cacheFile); + if (cacheUpdates) { + if (Files.exists(cacheFilePath)) { + try { + readCache(cacheFilePath); + return; + } catch (Throwable e) { + logger.error("Read updates cache failed", e); + } + } + } + sync(null); + } + + public void sync(Collection dirs) throws IOException { + logger.info("Syncing updates dir"); + Map newUpdatesDirMap = new HashMap<>(16); + try (DirectoryStream dirStream = Files.newDirectoryStream(Path.of(updatesDir))) { + for (final 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)) { + if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e))) + logger.warn("Not update dir: '{}'", name); + continue; + } + + // Add from previous map (it's guaranteed to be non-null) + if (dirs != null && !dirs.contains(name)) { + HashedDir hdir = updatesDirMap.get(name); + if (hdir != null) { + newUpdatesDirMap.put(name, hdir); + continue; + } + } + + // Sync and sign update dir + logger.info("Syncing '{}' update dir", name); + HashedDir updateHDir = new HashedDir(updateDir, null, true, true); + newUpdatesDirMap.put(name, updateHDir); + } + } + updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); + if (cacheUpdates) { + try { + writeCache(Path.of(cacheFile)); + } catch (Throwable e) { + logger.error("Write updates cache failed", e); + } + } + server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server)); + } + + public HashedDir getUpdatesDir(String updateName) { + if(updateName == null) { + return null; + } + return updatesDirMap.get(updateName); + } + + @Override + public void init(LaunchServer server) { + super.init(server); + try { + if (!IOHelper.isDir(Path.of(updatesDir))) + Files.createDirectory(Path.of(updatesDir)); + readUpdatesDir(); + } catch (IOException e) { + logger.error("Updates not synced", e); + } + try { + Path profilesDirPath = Path.of(profilesDir); + if (!IOHelper.isDir(profilesDirPath)) + Files.createDirectory(profilesDirPath); + readProfilesDir(); + } catch (IOException e) { + logger.error("Profiles not synced", e); + } + } + + public static void execute(HashedDir dir, Path updatesDirPath, List actions) throws IOException { + for(var action : actions) { + execute(dir, updatesDirPath, action); + } + } + + public static void execute(HashedDir dir, Path updatesDirPath, ProfileAction action) throws IOException { + switch (action.type()) { + case UPLOAD -> { + Path target = updatesDirPath.resolve(action.target()); + if(action.source() == null) { + IOHelper.createParentDirs(target); + IOHelper.transfer(action.input().get(), target); + } else { + Path source = Path.of(action.source()); + if(source.toAbsolutePath().equals(target.toAbsolutePath())) { + return; + } + if(action.deleteSource()) { + IOHelper.createParentDirs(target); + IOHelper.move(source, target); + } else { + IOHelper.createParentDirs(target); + IOHelper.copy(source, target); + } + } + } + case COPY -> { + Path source = updatesDirPath.resolve(action.source()); + Path target = updatesDirPath.resolve(action.target()); + if(source.toAbsolutePath().equals(target.toAbsolutePath())) { + return; + } + IOHelper.createParentDirs(target); + IOHelper.copy(source, target); + } + case MOVE -> { + Path source = updatesDirPath.resolve(action.source()); + Path target = updatesDirPath.resolve(action.target()); + if(source.toAbsolutePath().equals(target.toAbsolutePath())) { + return; + } + IOHelper.createParentDirs(target); + IOHelper.move(source, target); + } + case DELETE -> { + Path target = updatesDirPath.resolve(action.target()); + if(Files.isDirectory(target)) { + IOHelper.deleteDir(target, true); + } + } + } + } + + @Override + public Map getCommands() { + return Map.of( "sync", + new SubCommand("[]", "sync all") { + @Override + public void invoke(String... args) throws Exception { + try { + if (!IOHelper.isDir(Path.of(updatesDir))) + Files.createDirectory(Path.of(updatesDir)); + readUpdatesDir(); + } catch (IOException e) { + logger.error("Updates not synced", e); + } + try { + Path profilesDirPath = Path.of(profilesDir); + if (!IOHelper.isDir(profilesDirPath)) + Files.createDirectory(profilesDirPath); + readProfilesDir(); + } catch (IOException e) { + logger.error("Profiles not synced", e); + } + logger.info("Profiles and updates synced"); + } + } + ); + } + + public class LocalProfile implements CompletedProfile { + private ClientProfile profile; + private HashedDir clientDir; + private HashedDir assetDir; + private Path configPath; + + public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir) { + this.profile = profile; + this.clientDir = clientDir; + this.assetDir = assetDir; + this.configPath = Path.of(profilesDir).resolve(profile.getDir()); + } + + public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir, Path configPath) { + this.profile = profile; + this.clientDir = clientDir; + this.assetDir = assetDir; + this.configPath = configPath; + } + + @Override + public String getTag() { + return null; + } + + @Override + public ClientProfile getProfile() { + return profile; + } + + @Override + public HashedDir getClientDir() { + return clientDir; + } + + @Override + public HashedDir getAssetDir() { + return assetDir; + } + + @Override + public UUID getUuid() { + return profile.getUUID(); + } + + @Override + public String getName() { + return profile.getTitle(); + } + + @Override + public String getDescription() { + return profile.getInfo(); + } + + @Override + public String getDefaultTag() { + return null; + } + + public Path getConfigPath() { + return configPath; + } + } + + private final class ProfilesFileVisitor extends SimpleFileVisitor { + private final Map result; + private final Logger logger = LogManager.getLogger(); + + private ProfilesFileVisitor(Map result) { + this.result = result; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + logger.info("Syncing '{}' profile", IOHelper.getFileName(file)); + + // Read profile + ClientProfile profile; + try (BufferedReader reader = IOHelper.newReader(file)) { + profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class); + } + profile.verify(); + + LocalProfile localProfile = new LocalProfile(profile, getUpdatesDir(profile.getDir()), getUpdatesDir(profile.getAssetDir()), file); + result.put(localProfile.getUuid(), localProfile); + return super.visitFile(file, attrs); + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfileProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfileProvider.java deleted file mode 100644 index b5580021..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfileProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -package pro.gravit.launchserver.auth.profiles; - -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; -import pro.gravit.launchserver.socket.Client; -import pro.gravit.utils.ProviderMap; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -public abstract class ProfileProvider { - public static final ProviderMap providers = new ProviderMap<>("ProfileProvider"); - private static boolean registredProviders = false; - protected transient LaunchServer server; - - public static void registerProviders() { - if (!registredProviders) { - providers.register("local", LocalProfileProvider.class); - registredProviders = true; - } - } - - public void init(LaunchServer server) { - this.server = server; - } - - public abstract void sync() throws IOException; - - public abstract Set getProfiles(); - - public abstract void addProfile(ClientProfile profile) throws IOException; - - public abstract void deleteProfile(ClientProfile profile) throws IOException; - - public void close() { - - } - - public ClientProfile getProfile(UUID uuid) { - for(var e : getProfiles()) { - if(e.getUUID().equals(uuid)) { - return e; - } - } - return null; - } - - public ClientProfile getProfile(String title) { - for(var e : getProfiles()) { - if(e.getTitle().equals(title)) { - return e; - } - } - return null; - } - - public List getProfiles(Client client) { - List profileList; - Set serverProfiles = getProfiles(); - if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) { - profileList = new ArrayList<>(4); - for (ClientProfile profile : serverProfiles) { - if (protectHandler.canGetProfile(profile, client)) { - profileList.add(profile); - } - } - } else { - profileList = List.copyOf(serverProfiles); - } - return profileList; - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfilesProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfilesProvider.java new file mode 100644 index 00000000..d813b9a9 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/profiles/ProfilesProvider.java @@ -0,0 +1,87 @@ +package pro.gravit.launchserver.auth.profiles; + +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.utils.ProviderMap; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public abstract class ProfilesProvider { + public static final ProviderMap providers = new ProviderMap<>("ProfileProvider"); + private static boolean registredProviders = false; + protected transient LaunchServer server; + + public static void registerProviders() { + if (!registredProviders) { + providers.register("local", LocalProfilesProvider.class); + registredProviders = true; + } + } + + public void init(LaunchServer server) { + this.server = server; + } + + public abstract UncompletedProfile create(String name, String description, CompletedProfile basic); + public abstract void delete(UncompletedProfile profile); + public abstract Set getProfiles(Client client); + public abstract CompletedProfile pushUpdate(UncompletedProfile profile, + String tag, + ClientProfile clientProfile, + List assetActions, + List clientActions, + List flags) throws IOException; + public abstract void download(CompletedProfile profile, Map files, boolean assets) throws IOException; + public abstract HashedDir getUnconnectedDirectory(String name); + public abstract CompletedProfile get(UUID uuid, String tag); + public abstract CompletedProfile get(String name, String tag); + public CompletedProfile get(UncompletedProfile profile) { + if(profile == null) { + return null; + } + return get(profile.getUuid(), null); + } + + public void close() { + + } + + public interface UncompletedProfile { + UUID getUuid(); + String getName(); + String getDescription(); + String getDefaultTag(); + } + + public interface CompletedProfile extends UncompletedProfile { + String getTag(); + ClientProfile getProfile(); + HashedDir getClientDir(); + HashedDir getAssetDir(); + } + + public record ProfileAction(ProfileActionType type, String source, String target, Supplier input, Consumer output, boolean deleteSource) { + public enum ProfileActionType { + UPLOAD, COPY, MOVE, DELETE + } + public static ProfileAction upload(Path source, String target, boolean deleteSource) { + return new ProfileAction(ProfileActionType.UPLOAD, source.toString(), target, null, null, deleteSource); + } + + public static ProfileAction upload(Supplier input, String target) { + return new ProfileAction(ProfileActionType.UPLOAD, null, target, input, null, false); + } + } + + public enum UpdateFlag { + USE_DEFAULT_ASSETS, DELETE_SOURCE_FILES + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/StdProtectHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/StdProtectHandler.java index 578c7f21..0ba896db 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/StdProtectHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/StdProtectHandler.java @@ -2,18 +2,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; -import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.auth.AuthResponse; -import java.util.*; - -public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler { +public class StdProtectHandler extends ProtectHandler { private transient final Logger logger = LogManager.getLogger(); - public Map> profileWhitelist = new HashMap<>(); - public List allowUpdates = new ArrayList<>(); @Override public boolean allowGetAccessToken(AuthResponse.AuthContext context) { @@ -22,38 +15,6 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) { @Override public void init(LaunchServer server) { - if (profileWhitelist != null && !profileWhitelist.isEmpty()) { - logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'"); - } - } - @Override - public boolean canGetProfile(ClientProfile profile, Client client) { - return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client); - } - - @Override - public boolean canChangeProfile(ClientProfile profile, Client client) { - return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client); - } - - @Override - public boolean canGetUpdates(String updatesDirName, Client client) { - return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName)); - } - - private boolean isWhitelisted(String property, ClientProfile profile, Client client) { - if (client.permissions != null) { - String permByUUID = property.formatted(profile.getUUID()); - if (client.permissions.hasPerm(permByUUID)) { - return true; - } - String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT)); - if (client.permissions.hasPerm(permByTitle)) { - return true; - } - } - List allowedUsername = profileWhitelist.get(profile.getTitle()); - return allowedUsername != null && allowedUsername.contains(client.username); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/interfaces/ProfilesProtectHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/interfaces/ProfilesProtectHandler.java deleted file mode 100644 index f2b2d325..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/interfaces/ProfilesProtectHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package pro.gravit.launchserver.auth.protect.interfaces; - -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launchserver.socket.Client; - -public interface ProfilesProtectHandler { - default boolean canGetProfiles(Client client) { - return true; - } - - default boolean canGetProfile(ClientProfile profile, Client client) { - return true; - } - - default boolean canChangeProfile(ClientProfile profile, Client client) { - return client.isAuth; - } - - default boolean canGetUpdates(String updatesDirName, Client client) { - return true; - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/LocalUpdatesProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/LocalUpdatesProvider.java index fcea2fcd..2a7ad5d1 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/LocalUpdatesProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/LocalUpdatesProvider.java @@ -2,226 +2,81 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.core.hasher.HashedDir; -import pro.gravit.launcher.core.serialize.HInput; -import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent; import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.SecurityHelper; -import java.io.*; -import java.nio.file.DirectoryStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.*; -import java.util.stream.Stream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; public class LocalUpdatesProvider extends UpdatesProvider { - private final transient Logger logger = LogManager.getLogger(); - public String cacheFile = ".updates-cache"; + private transient final Logger logger = LogManager.getLogger(); public String updatesDir = "updates"; - public boolean cacheUpdates = true; - private volatile transient Map updatesDirMap; - - private void writeCache(Path file) throws IOException { - try (HOutput output = new HOutput(IOHelper.newOutput(file))) { - output.writeLength(updatesDirMap.size(), 0); - for (Map.Entry entry : updatesDirMap.entrySet()) { - output.writeString(entry.getKey(), 0); - entry.getValue().write(output); - } - } - logger.debug("Saved {} updates to cache", updatesDirMap.size()); - } - - private void readCache(Path file) throws IOException { - Map updatesDirMap = new HashMap<>(16); - try (HInput input = new HInput(IOHelper.newInput(file))) { - int size = input.readLength(0); - for (int i = 0; i < size; ++i) { - String name = input.readString(0); - HashedDir dir = new HashedDir(input); - updatesDirMap.put(name, dir); - } - } - logger.debug("Found {} updates from cache", updatesDirMap.size()); - this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap); - } - - public void readUpdatesFromCache() throws IOException { - readCache(Path.of(cacheFile)); - } - - public void readUpdatesDir() throws IOException { - var cacheFilePath = Path.of(cacheFile); - if (cacheUpdates) { - if (Files.exists(cacheFilePath)) { - try { - readCache(cacheFilePath); - return; - } catch (Throwable e) { - logger.error("Read updates cache failed", e); - } - } - } - sync(null); - } + public String binaryName = "Launcher"; + public Map urls = new HashMap<>(Map.of( + UpdateVariant.JAR, "http://localhost:9274/Launcher.jar", + UpdateVariant.EXE, "http://localhost:9274/Launcher.exe" + )); + private final transient Map hashMap = new HashMap<>(); @Override public void init(LaunchServer server) { super.init(server); try { - if (!IOHelper.isDir(Path.of(updatesDir))) - Files.createDirectory(Path.of(updatesDir)); + sync(UpdateVariant.JAR); + sync(UpdateVariant.EXE); } catch (IOException e) { - logger.error("Updates not synced", e); + logger.error("Error when syncing binaries", e); } } @Override - public void syncInitially() throws IOException { - readUpdatesDir(); + public void pushUpdate(Map files) throws IOException { + for(var e : files.entrySet()) { + IOHelper.copy(e.getValue(), getUpdate(e.getKey())); + sync(e.getKey()); + } } - public void sync(Collection dirs) throws IOException { - logger.info("Syncing updates dir"); - Map newUpdatesDirMap = new HashMap<>(16); - try (DirectoryStream dirStream = Files.newDirectoryStream(Path.of(updatesDir))) { - for (final Path updateDir : dirStream) { - if (Files.isHidden(updateDir)) - continue; // Skip hidden + public void sync(UpdateVariant variant) throws IOException { + var source = getUpdate(variant); + if(!Files.exists(source)) { + logger.warn("Dont exist {} binary", variant); + return; + } + byte[] hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, source); + hashMap.put(variant, hash); + } - // Resolve name and verify is dir - String name = IOHelper.getFileName(updateDir); - if (!IOHelper.isDir(updateDir)) { - if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e))) - logger.warn("Not update dir: '{}'", name); - continue; - } - - // Add from previous map (it's guaranteed to be non-null) - if (dirs != null && !dirs.contains(name)) { - HashedDir hdir = updatesDirMap.get(name); - if (hdir != null) { - newUpdatesDirMap.put(name, hdir); - continue; - } - } - - // Sync and sign update dir - logger.info("Syncing '{}' update dir", name); - HashedDir updateHDir = new HashedDir(updateDir, null, true, true); - newUpdatesDirMap.put(name, updateHDir); + public Path getUpdate(UpdateVariant variant) { + String fileName; + switch (variant) { + case JAR -> { + fileName = binaryName.concat(".jar"); + } + case EXE -> { + fileName = binaryName.concat(".exe"); + } + default -> { + fileName = binaryName; } } - updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); - if (cacheUpdates) { - try { - writeCache(Path.of(cacheFile)); - } catch (Throwable e) { - logger.error("Write updates cache failed", e); - } + return Path.of(updatesDir).resolve(fileName); + } + + @Override + public UpdateInfo checkUpdates(UpdateVariant variant, byte[] digest) { + byte[] hash = hashMap.get(variant); + if (hash == null) { + return null; // We dont have this file } - server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server)); - } - - @Override - public HashedDir getUpdatesDir(String updateName) { - return updatesDirMap.get(updateName); - } - - private Path resolveUpdateName(String updateName) { - if(updateName == null) { - return Path.of(updatesDir); + if(Arrays.equals(digest, hash)) { + return null; // Launcher already updated } - return Path.of(updatesDir).resolve(updateName); - } - - @Override - public void upload(String updateName, Map files, boolean deleteAfterUpload) throws IOException { - var path = resolveUpdateName(updateName); - for(var e : files.entrySet()) { - var target = path.resolve(e.getKey()); - var source = e.getValue(); - IOHelper.createParentDirs(target); - if(deleteAfterUpload) { - Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); - } else { - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - } - } - } - - @Override - public OutputStream upload(String updateName, String file) throws IOException { - var path = resolveUpdateName(updateName); - var target = path.resolve(file); - IOHelper.createParentDirs(target); - return new FileOutputStream(target.toFile()); - } - - @Override - public Map download(String updateName, List files) { - var path = resolveUpdateName(updateName); - Map map = new HashMap<>(); - for(var e : files) { - map.put(e, path.resolve(e)); - } - return map; - } - - @Override - public void download(String updateName, Map files) throws IOException { - var path = resolveUpdateName(updateName); - for(var e : files.entrySet()) { - var source = path.resolve(e.getKey()); - var target = e.getValue(); - IOHelper.copy(source, target); - } - } - - @Override - public InputStream download(String updateName, String path) throws IOException { - return new FileInputStream(resolveUpdateName(updateName).resolve(path).toFile()); - } - - @Override - public void move(Map files) throws IOException { - for(var e : files.entrySet()) { - var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path()); - var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path()); - IOHelper.move(source, target); - } - } - - @Override - public void copy(Map files) throws IOException { - for(var e : files.entrySet()) { - var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path()); - var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path()); - IOHelper.copy(source, target); - } - } - - @Override - public void delete(String updateName, List files) throws IOException { - var path = resolveUpdateName(updateName); - for(var e : files) { - var target = path.resolve(e); - Files.delete(target); - } - } - - @Override - public void delete(String updateName) throws IOException { - var path = resolveUpdateName(updateName); - IOHelper.deleteDir(path, true); - } - - @Override - public void create(String updateName) throws IOException { - var path = resolveUpdateName(updateName); - Files.createDirectories(path); + return new UpdateInfo(urls.get(variant)); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/UpdatesProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/UpdatesProvider.java index 52fdd9c3..568bdaa9 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/UpdatesProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/updates/UpdatesProvider.java @@ -1,26 +1,15 @@ package pro.gravit.launchserver.auth.updates; -import pro.gravit.launcher.core.hasher.HashedDir; -import pro.gravit.launcher.core.hasher.HashedEntry; import pro.gravit.launchserver.LaunchServer; import pro.gravit.utils.ProviderMap; -import pro.gravit.utils.helper.IOHelper; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; +import java.net.URL; import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; import java.util.Map; public abstract class UpdatesProvider { + public static final ProviderMap providers = new ProviderMap<>("UpdatesProvider"); private static boolean registredProviders = false; protected transient LaunchServer server; @@ -36,81 +25,17 @@ public void init(LaunchServer server) { this.server = server; } - public void sync() throws IOException { - sync(null); - } - - public abstract void syncInitially() throws IOException; - - public abstract void sync(Collection updateNames) throws IOException; - - public abstract HashedDir getUpdatesDir(String updateName); - - public abstract void upload(String updateName, Map files, boolean deleteAfterUpload) throws IOException; - - public abstract OutputStream upload(String updateName, String file) throws IOException; - - public void upload(String updateName, Path dir, boolean deleteAfterUpload) throws IOException { - if(!Files.isDirectory(dir)) { - throw new UnsupportedEncodingException(String.format("%s is not a directory", dir)); - } - Map map = new HashMap<>(); - IOHelper.walk(dir, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - map.put(dir.relativize(file).toString(), file); - return FileVisitResult.CONTINUE; - } - }, true); - upload(updateName, map, deleteAfterUpload); - } - - public abstract Map download(String updateName, List files) throws IOException; - - public abstract void download(String updateName, Map files) throws IOException; - - public abstract InputStream download(String updateName, String path) throws IOException; - - public abstract void move(Map files) throws IOException; - - public void move(String updateName, String newUpdateName) throws IOException { - create(newUpdateName); - var updatesDir = getUpdatesDir(updateName); - Map map = new HashMap<>(); - updatesDir.walk("/", (path, name, entry) -> { - map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path)); - return HashedDir.WalkAction.CONTINUE; - }); - move(map); - delete(updateName); - } - - public abstract void copy(Map files) throws IOException; - - public void copy(String updateName, String newUpdateName) throws IOException { - create(newUpdateName); - var updatesDir = getUpdatesDir(updateName); - Map map = new HashMap<>(); - updatesDir.walk("/", (path, name, entry) -> { - map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path)); - return HashedDir.WalkAction.CONTINUE; - }); - copy(map); - } - - public abstract void delete(String updateName, List files) throws IOException; - - public abstract void delete(String updateName) throws IOException; - - public abstract void create(String updateName) throws IOException; + public abstract void pushUpdate(Map files) throws IOException; + public abstract UpdateInfo checkUpdates(UpdateVariant variant, byte[] digest); public void close() { - } - public record UpdateNameAndFile(String updateName, String path) { - public static UpdateNameAndFile of(String updateName, String path) { - return new UpdateNameAndFile(updateName, path); - } + public enum UpdateVariant { + JAR, EXE + } + + public record UpdateInfo(String url) { + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/EXELauncherBinary.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/EXELauncherBinary.java index 763d9912..0428299a 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/EXELauncherBinary.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/EXELauncherBinary.java @@ -1,6 +1,7 @@ package pro.gravit.launchserver.binary; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.utils.helper.IOHelper; import java.io.IOException; @@ -9,14 +10,16 @@ public class EXELauncherBinary extends LauncherBinary { public EXELauncherBinary(LaunchServer server) { - super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe"); + super(server, "Launcher-%s.exe"); + } + + @Override + public UpdatesProvider.UpdateVariant getVariant() { + return UpdatesProvider.UpdateVariant.EXE; } @Override public void build() throws IOException { - if (IOHelper.isFile(syncBinaryFile)) { - Files.delete(syncBinaryFile); - } } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/JARLauncherBinary.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/JARLauncherBinary.java index 4a1d28b5..bb86ce9b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/JARLauncherBinary.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/JARLauncherBinary.java @@ -2,6 +2,7 @@ import pro.gravit.launcher.base.Launcher; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.binary.tasks.*; import java.io.IOException; @@ -23,7 +24,7 @@ public final class JARLauncherBinary extends LauncherBinary { public final Map files; public JARLauncherBinary(LaunchServer server) throws IOException { - super(server, resolve(server, ".jar"), "Launcher-%s.jar"); + super(server, "Launcher-%s.jar"); count = new AtomicLong(0); runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); buildDir = server.dir.resolve("build"); @@ -36,6 +37,11 @@ public JARLauncherBinary(LaunchServer server) throws IOException { } } + @Override + public UpdatesProvider.UpdateVariant getVariant() { + return UpdatesProvider.UpdateVariant.JAR; + } + @Override public void init() { tasks.add(new PrepareBuildTask(server)); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/LauncherBinary.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/LauncherBinary.java index 242541d4..bbc24d7a 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/LauncherBinary.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/LauncherBinary.java @@ -1,6 +1,7 @@ package pro.gravit.launchserver.binary; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.binary.tasks.LauncherBuildTask; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.SecurityHelper; @@ -12,13 +13,10 @@ public abstract class LauncherBinary extends BinaryPipeline { public final LaunchServer server; - public final Path syncBinaryFile; - private volatile byte[] digest; - protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) { + protected LauncherBinary(LaunchServer server, String nameFormat) { super(server.tmpDir.resolve("build"), nameFormat); this.server = server; - syncBinaryFile = binaryFile; } public static Path resolve(LaunchServer server, String ext) { @@ -40,30 +38,17 @@ public void build() throws IOException { logger.info("Task {} processed from {} millis", task.getName(), time_task); } long time_end = System.currentTimeMillis(); - server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true); + if(thisPath != null) { + server.config.updatesProvider.pushUpdate(Map.of(getVariant(), thisPath)); + } else { + logger.warn("Missing {} binary file", getVariant()); + } IOHelper.deleteDir(buildDir, false); logger.info("Build successful from {} millis", time_end - time_start); } - public final boolean exists() { - return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile); - } - - public final byte[] getDigest() { - return digest; - } + public abstract UpdatesProvider.UpdateVariant getVariant(); public void init() { } - - public final boolean sync() { - try { - var target = syncBinaryFile.toString(); - var path = server.config.updatesProvider.download(null, List.of(target)).get(target); - digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path)); - return true; - } catch (Throwable e) { - return false; - } - } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/Command.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/Command.java index 87d27f25..e2bbb62b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/Command.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/Command.java @@ -44,7 +44,7 @@ protected boolean showApplyDialog(String text) throws IOException { return response.equals("y"); } - protected Downloader downloadWithProgressBar(String taskName, List list, String baseUrl, Path targetDir) throws Exception { + protected static Downloader downloadWithProgressBar(String taskName, List list, String baseUrl, Path targetDir) throws Exception { long total = 0; for (Downloader.SizedFile file : list) { if(file.size < 0) { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/basic/BuildCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/basic/BuildCommand.java index cfbc5164..68b96964 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/basic/BuildCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/basic/BuildCommand.java @@ -21,6 +21,5 @@ public String getUsageDescription() { @Override public void invoke(String... args) throws Exception { server.buildLauncherBinaries(); - server.syncLauncherBinaries(); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java index bd8a8567..e9a30685 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java @@ -33,10 +33,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand // Register sync commands BaseCommandCategory updates = new BaseCommandCategory(); - updates.registerCommand("indexAsset", new IndexAssetCommand(server)); - updates.registerCommand("unindexAsset", new UnindexAssetCommand(server)); - updates.registerCommand("downloadAsset", new DownloadAssetCommand(server)); - updates.registerCommand("downloadClient", new DownloadClientCommand(server)); + //updates.registerCommand("indexAsset", new IndexAssetCommand(server)); + //updates.registerCommand("unindexAsset", new UnindexAssetCommand(server)); updates.registerCommand("sync", new SyncCommand(server)); updates.registerCommand("profile", new ProfilesCommand(server)); Category updatesCategory = new Category(updates, "updates", "Update and Sync Management"); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadAssetCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadAssetCommand.java deleted file mode 100644 index db2c7a4a..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadAssetCommand.java +++ /dev/null @@ -1,137 +0,0 @@ -package pro.gravit.launchserver.command.hash; - -import com.google.gson.JsonObject; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.Launcher; -import pro.gravit.launcher.base.Downloader; -import pro.gravit.launcher.core.hasher.HashedDir; -import pro.gravit.launchserver.HttpRequester; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; -import pro.gravit.utils.helper.IOHelper; - -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public final class DownloadAssetCommand extends Command { - private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json"; - private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/"; - private transient final Logger logger = LogManager.getLogger(); - - public DownloadAssetCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return "[version] [dir] (mojang/mirror)"; - } - - @Override - public String getUsageDescription() { - return "Download asset dir"; - } - - @Override - public void invoke(String... args) throws Exception { - verifyArgs(args, 1); - //Version version = Version.byName(args[0]); - String versionName = args[0]; - String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets"); - String type = args.length > 2 ? args[2] : "mojang"; - Path assetDir = server.createTempDirectory("assets"); - var updatesDir = server.config.updatesProvider.getUpdatesDir(dirName); - if(updatesDir == null) { - updatesDir = new HashedDir(); - server.config.updatesProvider.create(dirName); - } - - // Create asset dir - if (Files.notExists(assetDir)) { - logger.info("Creating asset dir: '{}'", dirName); - Files.createDirectory(assetDir); - } - - if (type.equals("mojang")) { - HttpRequester requester = new HttpRequester(); - logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL); - var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow(); - String profileUrl = null; - for (var e : versions.versions) { - if (e.id.equals(versionName)) { - profileUrl = e.url; - break; - } - } - if (profileUrl == null) { - logger.error("Version {} not found", versionName); - return; - } - logger.info("Fetch profile {} from {}", versionName, profileUrl); - var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow(); - String assetsIndexUrl = profileInfo.assetIndex.url; - String assetIndex = profileInfo.assetIndex.id; - Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json"); - logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl); - JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow(); - JsonObject objects = assets.get("objects").getAsJsonObject(); - try (Writer writer = IOHelper.newWriter(indexPath)) { - logger.info("Save {}", indexPath); - Launcher.gsonManager.configGson.toJson(assets, writer); - } - if (!assetIndex.equals(versionName)) { - Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json"); - logger.info("Copy {} into {}", indexPath, targetPath); - Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING); - } - List toDownload = new ArrayList<>(128); - for (var e : objects.entrySet()) { - var value = e.getValue().getAsJsonObject(); - var hash = value.get("hash").getAsString(); - hash = hash.substring(0, 2) + "/" + hash; - var size = value.get("size").getAsLong(); - var path = "objects/" + hash; - if (updatesDir.tryFindRecursive(path).isFound()) { - continue; - } - toDownload.add(new Downloader.SizedFile(hash, path, size)); - } - logger.info("Download {} files", toDownload.size()); - Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir); - downloader.getFuture().get(); - } else { - // Download required asset - logger.info("Downloading asset, it may take some time"); - //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir); - server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName); - } - - server.config.updatesProvider.upload(dirName, assetDir, true); - - // Finished - server.syncUpdatesDir(Collections.singleton(dirName)); - logger.info("Asset successfully downloaded: '{}'", dirName); - } - - public record MiniVersionInfo(String id, String url) { - - } - - public record MinecraftVersions(List versions) { - - } - - public record MinecraftAssetIndexInfo(String id, String url) { - - } - - public record MiniVersion(MinecraftAssetIndexInfo assetIndex) { - - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadClientCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadClientCommand.java deleted file mode 100644 index cdedf5d3..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/DownloadClientCommand.java +++ /dev/null @@ -1,108 +0,0 @@ -package pro.gravit.launchserver.command.hash; - -import com.google.gson.JsonElement; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.Launcher; -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launcher.base.profiles.ClientProfileBuilder; -import pro.gravit.launcher.base.profiles.ClientProfileVersions; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; -import pro.gravit.launchserver.helper.MakeProfileHelper; -import pro.gravit.utils.command.CommandException; -import pro.gravit.utils.helper.IOHelper; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.UUID; - -public final class DownloadClientCommand extends Command { - - private transient final Logger logger = LogManager.getLogger(); - - public DownloadClientCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return "[version] [dir] (mirror/generate)"; - } - - @Override - public String getUsageDescription() { - return "Download client dir"; - } - - @Override - public void invoke(String... args) throws IOException, CommandException { - verifyArgs(args, 2); - //Version version = Version.byName(args[0]); - String versionName = args[0]; - String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]); - Path clientDir = server.createTempDirectory("client"); - server.config.updatesProvider.create(dirName); - - boolean isMirrorClientDownload = false; - if (args.length > 2) { - isMirrorClientDownload = args[2].equals("mirror"); - } - - // Download required client - logger.info("Downloading client, it may take some time"); - //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir); - server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName); - - // Create profile file - logger.info("Creaing profile file: '{}'", dirName); - ClientProfile clientProfile = null; - if (isMirrorClientDownload) { - try { - JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName); - clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class); - var builder = new ClientProfileBuilder(clientProfile); - builder.setTitle(dirName); - builder.setDir(dirName); - builder.setUuid(UUID.randomUUID()); - clientProfile = builder.createClientProfile(); - if (clientProfile.getServers() != null) { - ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile(); - if (serverProfile != null) { - serverProfile.name = dirName; - } - } - } catch (Exception e) { - logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName); - isMirrorClientDownload = false; - } - } - if (!isMirrorClientDownload) { - try { - String internalVersion = versionName; - if (internalVersion.contains("-")) { - internalVersion = internalVersion.substring(0, versionName.indexOf('-')); - } - ClientProfile.Version version = ClientProfile.Version.of(internalVersion); - if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) { - logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk"); - } - MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version); - for (MakeProfileHelper.MakeProfileOption option : options) { - logger.debug("Detected option {}", option.getClass().getSimpleName()); - } - clientProfile = MakeProfileHelper.makeProfile(version, dirName, options); - } catch (Throwable e) { - isMirrorClientDownload = true; - } - } - server.config.profileProvider.addProfile(clientProfile); - server.config.updatesProvider.upload(dirName, clientDir, true); - - // Finished - server.syncProfilesDir(); - server.syncUpdatesDir(Collections.singleton(dirName)); - logger.info("Client successfully downloaded: '{}'", dirName); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/IndexAssetCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/IndexAssetCommand.java index fe248417..1fe918b0 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/IndexAssetCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/IndexAssetCommand.java @@ -52,7 +52,7 @@ public String getUsageDescription() { @Override public void invoke(String... args) throws Exception { - verifyArgs(args, 3); + /*verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); @@ -78,7 +78,7 @@ public void invoke(String... args) throws Exception { // Finished server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); - logger.info("Asset successfully indexed: '{}'", inputAssetDirName); + logger.info("Asset successfully indexed: '{}'", inputAssetDirName);*/ } public static class IndexObject { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/UnindexAssetCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/UnindexAssetCommand.java index 89ce4bc8..02a66cbb 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/UnindexAssetCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/hash/UnindexAssetCommand.java @@ -37,7 +37,7 @@ public String getUsageDescription() { @Override public void invoke(String... args) throws Exception { - verifyArgs(args, 3); + /*verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); @@ -73,6 +73,6 @@ public void invoke(String... args) throws Exception { // Finished server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); - logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString()); + logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString());*/ } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CloneProfileCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CloneProfileCommand.java index ba017cfd..7271f1ee 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CloneProfileCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CloneProfileCommand.java @@ -5,6 +5,7 @@ import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfileBuilder; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.command.Command; import pro.gravit.utils.helper.IOHelper; @@ -34,26 +35,14 @@ public String getUsageDescription() { @Override public void invoke(String... args) throws Exception { verifyArgs(args, 2); - ClientProfile profile; + ProfilesProvider.CompletedProfile profile; try { UUID uuid = UUID.fromString(args[0]); - profile = server.config.profileProvider.getProfile(uuid); + profile = server.config.profilesProvider.get(uuid, null); } catch (IllegalArgumentException ex) { - profile = server.config.profileProvider.getProfile(args[0]); + profile = server.config.profilesProvider.get(args[0], null); } - var builder = new ClientProfileBuilder(profile); - builder.setTitle(args[1]); - builder.setUuid(UUID.randomUUID()); - if(profile.getServers().size() == 1) { - profile.getServers().getFirst().name = args[1]; - } - logger.info("Copy {} to {}", profile.getDir(), args[1]); - server.config.updatesProvider.copy(profile.getDir(), args[1]); - builder.setDir(args[1]); - profile = builder.createClientProfile(); - server.config.profileProvider.addProfile(profile); + server.config.profilesProvider.create(args[1], "Description", profile); logger.info("Profile {} cloned from {}", args[1], args[0]); - server.syncProfilesDir(); - server.syncUpdatesDir(List.of(args[1])); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CreateProfileCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CreateProfileCommand.java new file mode 100644 index 00000000..992190c3 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/CreateProfileCommand.java @@ -0,0 +1,140 @@ +package pro.gravit.launchserver.command.profiles; + +import com.google.gson.JsonElement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.base.Downloader; +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; +import pro.gravit.launcher.base.profiles.ClientProfileVersions; +import pro.gravit.launchserver.HttpRequester; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; +import pro.gravit.launchserver.command.Command; +import pro.gravit.launchserver.helper.AssetsDirHelper; +import pro.gravit.launchserver.helper.MakeProfileHelper; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class CreateProfileCommand extends Command { + + private static final Logger logger = LogManager.getLogger(); + + public CreateProfileCommand(LaunchServer server) { + super(server); + } + + @Override + public String getArgsDescription() { + return "[version] [dir] (mirror/generate)"; + } + + @Override + public String getUsageDescription() { + return "Download client"; + } + + @Override + public void invoke(String... args) throws Exception { + verifyArgs(args, 2); + //Version version = Version.byName(args[0]); + String versionName = args[0]; + String dirName = args[1]; + Path clientDir = server.createTempDirectory("client"); + + boolean isMirrorProfileDownload = false; + if (args.length > 2) { + isMirrorProfileDownload = args[2].equals("mirror"); + } + + // Download required client + logger.info("Downloading client, it may take some time"); + //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir); + server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName); + + // Create profile file + logger.info("Creaing profile file: '{}'", dirName); + ClientProfile clientProfile = null; + try { + JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName); + clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class); + var builder = new ClientProfileBuilder(clientProfile); + builder.setTitle(dirName); + builder.setDir(dirName); + builder.setUuid(UUID.randomUUID()); + clientProfile = builder.createClientProfile(); + if (clientProfile.getServers() != null) { + ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile(); + if (serverProfile != null) { + serverProfile.name = dirName; + } + } + } catch (Exception e) { + logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName); + isMirrorProfileDownload = false; + } + if (!isMirrorProfileDownload) { + try { + String internalVersion = versionName; + if (internalVersion.contains("-")) { + internalVersion = internalVersion.substring(0, versionName.indexOf('-')); + } + ClientProfile.Version version = ClientProfile.Version.of(internalVersion); + if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) { + logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk"); + } + MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version); + for (MakeProfileHelper.MakeProfileOption option : options) { + logger.debug("Detected option {}", option.getClass().getSimpleName()); + } + clientProfile = MakeProfileHelper.makeProfile(version, dirName, options); + } catch (Throwable e) { + isMirrorProfileDownload = true; + } + } + pushClientAndDownloadAssets(server, clientProfile, clientDir); + + // Finished + logger.info("Client successfully downloaded: '{}'", dirName); + } + + public static ProfilesProvider.CompletedProfile pushClientAndDownloadAssets(LaunchServer server, ClientProfile clientProfile, Path clientDir) throws Exception { + var uncompleted = server.config.profilesProvider.create(clientProfile.getTitle(), "Description", null); + var completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of( + ProfilesProvider.ProfileAction.upload(clientDir, "", true) + ), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS)); + { + String assetIndexPath = String.format("indexes/%s.json", completed.getProfile().getAssetIndex()); + if (!completed.getAssetDir().tryFindRecursive(assetIndexPath).isFound()) { + Path assetDir = server.createTempDirectory("assets"); + HttpRequester requester = new HttpRequester(); + var assetInfo = AssetsDirHelper.getAssetInfo(requester, completed.getProfile().getAssetIndex()); + var toDownload = AssetsDirHelper.makeToDownloadFiles(assetInfo, completed.getAssetDir()); + logger.info("Download assets {}", completed.getProfile().getAssetIndex()); + Downloader downloader = downloadWithProgressBar(completed.getProfile().getAssetIndex(), + toDownload, AssetsDirHelper.RESOURCES_DOWNLOAD_URL, assetDir); + downloader.getFuture().get(); + byte[] assetIndexBytes = Launcher.gsonManager.configGson.toJson(assetInfo.assets()).getBytes(StandardCharsets.UTF_8); + completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, List.of( + ProfilesProvider.ProfileAction.upload(() -> { + return new ByteArrayInputStream(assetIndexBytes); + }, assetIndexPath), + ProfilesProvider.ProfileAction.upload(assetDir, "", true) + ), List.of( + ProfilesProvider.ProfileAction.upload(clientDir, "", true) + ), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS)); + } else { + completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of( + ProfilesProvider.ProfileAction.upload(clientDir, "", true) + ), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS)); + } + } + return completed; + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/DeleteProfileCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/DeleteProfileCommand.java index 66ffd76c..063ae87b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/DeleteProfileCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/DeleteProfileCommand.java @@ -4,6 +4,7 @@ import org.apache.logging.log4j.Logger; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.command.Command; import java.util.UUID; @@ -27,24 +28,18 @@ public String getUsageDescription() { @Override public void invoke(String... args) throws Exception { verifyArgs(args, 1); - ClientProfile profile; + ProfilesProvider.CompletedProfile profile; try { UUID uuid = UUID.fromString(args[0]); - profile = server.config.profileProvider.getProfile(uuid); + profile = server.config.profilesProvider.get(uuid, null); } catch (IllegalArgumentException ex) { - profile = server.config.profileProvider.getProfile(args[0]); + profile = server.config.profilesProvider.get(args[0], null); } if(profile == null) { logger.error("Profile {} not found", args[0]); return; } - logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir()); - if(!showApplyDialog("Continue?")) { - return; - } - logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID()); - server.config.profileProvider.deleteProfile(profile); - logger.info("Delete {}", profile.getDir()); - server.config.updatesProvider.delete(profile.getDir()); + logger.info("Delete {}", args[0]); + server.config.profilesProvider.delete(profile); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ListProfilesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ListProfilesCommand.java index 62ba80e9..c848451e 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ListProfilesCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ListProfilesCommand.java @@ -23,8 +23,8 @@ public String getUsageDescription() { @Override public void invoke(String... args) { - for(var profile : server.getProfiles()) { - logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : ""); + for(var profile : server.config.profilesProvider.getProfiles(null)) { + logger.info("{} ({})", profile.getName(), profile.getUuid()); } } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/MakeProfileCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/MakeProfileCommand.java deleted file mode 100644 index f8b580ff..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/MakeProfileCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package pro.gravit.launchserver.command.profiles; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; -import pro.gravit.launchserver.helper.MakeProfileHelper; - -import java.nio.file.Path; - -public class MakeProfileCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public MakeProfileCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return "[name] [minecraft version] [dir]"; - } - - @Override - public String getUsageDescription() { - return "make profile for any minecraft versions"; - } - - @Override - public void invoke(String... args) throws Exception { - verifyArgs(args, 3); - ClientProfile.Version version = parseClientVersion(args[1]); - MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(Path.of(args[2]), version); - for (MakeProfileHelper.MakeProfileOption option : options) { - logger.info("Detected option {}", option); - } - ClientProfile profile = MakeProfileHelper.makeProfile(version, args[0], options); - server.config.profileProvider.addProfile(profile); - logger.info("Profile {} created", args[0]); - server.syncProfilesDir(); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ProfilesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ProfilesCommand.java index c9d7a2c7..82ef5051 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ProfilesCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/ProfilesCommand.java @@ -6,8 +6,7 @@ public class ProfilesCommand extends Command { public ProfilesCommand(LaunchServer server) { super(server); - this.childCommands.put("make", new MakeProfileCommand(server)); - this.childCommands.put("save", new SaveProfilesCommand(server)); + this.childCommands.put("create", new CreateProfileCommand(server)); this.childCommands.put("clone", new CloneProfileCommand(server)); this.childCommands.put("list", new ListProfilesCommand(server)); this.childCommands.put("delete", new DeleteProfileCommand(server)); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/SaveProfilesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/SaveProfilesCommand.java deleted file mode 100644 index c14ad65d..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/profiles/SaveProfilesCommand.java +++ /dev/null @@ -1,46 +0,0 @@ -package pro.gravit.launchserver.command.profiles; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -import java.util.UUID; - -public class SaveProfilesCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public SaveProfilesCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return "[profile names...]"; - } - - @Override - public String getUsageDescription() { - return "load and save profile"; - } - - @Override - public void invoke(String... args) throws Exception { - verifyArgs(args, 1); - if (args.length > 0) { - for (String profileName : args) { - ClientProfile profile; - try { - UUID uuid = UUID.fromString(profileName); - profile = server.config.profileProvider.getProfile(uuid); - } catch (IllegalArgumentException ex) { - profile = server.config.profileProvider.getProfile(profileName); - } - server.config.profileProvider.addProfile(profile); - } - server.syncProfilesDir(); - } - } - -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java index 88472600..91fe2298 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java @@ -149,8 +149,8 @@ public void invoke(String... args) { case PROD -> printCheckResult("env", "", true); } - //Profiles - for (ClientProfile profile : server.getProfiles()) { + //Profiles TODO: Implement + /*for (ClientProfile profile : server.config.profilesProvider.getProfiles(null)) { boolean bad = false; String profileModuleName = "profiles.%s".formatted(profile.getTitle()); for (String exc : profile.getUpdateExclusions()) { @@ -177,7 +177,7 @@ public void invoke(String... args) { } if (!bad) printCheckResult(profileModuleName, "", true); - } + }*/ //Linux permissions check if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/TokenCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/TokenCommand.java index 6fd89943..b0994dd6 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/TokenCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/TokenCommand.java @@ -6,6 +6,7 @@ import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.command.Command; import pro.gravit.utils.command.SubCommand; @@ -28,9 +29,9 @@ public void invoke(String... args) throws Exception { public void invoke(String... args) { AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair(); boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]); - ClientProfile profile = null; - for (ClientProfile p : server.getProfiles()) { - if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) { + ProfilesProvider.UncompletedProfile profile = null; + for (var p : server.config.profilesProvider.getProfiles(null)) { + if (p.getName().equals(args[0]) || p.getUuid().toString().equals(args[0])) { profile = p; break; } @@ -42,7 +43,7 @@ public void invoke(String... args) { logger.error("AuthId {} not found", args[1]); return; } - String token = server.authManager.newCheckServerToken(profile != null ? profile.getUUID().toString() : args[0], pair.name, publicOnly); + String token = server.authManager.newCheckServerToken(profile != null ? profile.getUuid().toString() : args[0], pair.name, publicOnly); logger.info("Server token {} authId {}: {}", args[0], pair.name, token); } }); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncBinariesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncBinariesCommand.java deleted file mode 100644 index 9997b520..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncBinariesCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package pro.gravit.launchserver.command.sync; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -import java.io.IOException; - -public final class SyncBinariesCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public SyncBinariesCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return null; - } - - @Override - public String getUsageDescription() { - return "Resync launcher binaries"; - } - - @Override - public void invoke(String... args) throws IOException { - server.syncLauncherBinaries(); - logger.info("Binaries successfully resynced"); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncCommand.java index 099e6c25..3863e9ce 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncCommand.java @@ -6,12 +6,7 @@ public class SyncCommand extends Command { public SyncCommand(LaunchServer server) { super(server); - this.childCommands.put("profiles", new SyncProfilesCommand(server)); - this.childCommands.put("binaries", new SyncBinariesCommand(server)); - this.childCommands.put("updates", new SyncUpdatesCommand(server)); - this.childCommands.put("up", new SyncUPCommand(server)); this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server)); - this.childCommands.put("updatescache", new SyncUpdatesCacheCommand(server)); } @Override diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncProfilesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncProfilesCommand.java deleted file mode 100644 index 615036c3..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncProfilesCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -package pro.gravit.launchserver.command.sync; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -import java.io.IOException; - -public final class SyncProfilesCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public SyncProfilesCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return null; - } - - @Override - public String getUsageDescription() { - return "Resync profiles dir"; - } - - @Override - public void invoke(String... args) throws IOException { - server.syncProfilesDir(); - logger.info("Profiles successfully resynced"); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUPCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUPCommand.java deleted file mode 100644 index b82a9460..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUPCommand.java +++ /dev/null @@ -1,35 +0,0 @@ -package pro.gravit.launchserver.command.sync; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -import java.io.IOException; - -public final class SyncUPCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public SyncUPCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return null; - } - - @Override - public String getUsageDescription() { - return "Resync profiles & updates dirs"; - } - - @Override - public void invoke(String... args) throws IOException { - server.syncProfilesDir(); - logger.info("Profiles successfully resynced"); - - server.syncUpdatesDir(null); - logger.info("Updates dir successfully resynced"); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCacheCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCacheCommand.java deleted file mode 100644 index 19c30fac..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCacheCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package pro.gravit.launchserver.command.sync; - -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -public class SyncUpdatesCacheCommand extends Command { - public SyncUpdatesCacheCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return null; - } - - @Override - public String getUsageDescription() { - return "sync updates cache"; - } - - @Override - public void invoke(String... args) throws Exception { - server.updatesManager.readUpdatesFromCache(); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCommand.java deleted file mode 100644 index 1b294c97..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/sync/SyncUpdatesCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package pro.gravit.launchserver.command.sync; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public final class SyncUpdatesCommand extends Command { - private transient final Logger logger = LogManager.getLogger(); - - public SyncUpdatesCommand(LaunchServer server) { - super(server); - } - - @Override - public String getArgsDescription() { - return "[subdirs...]"; - } - - @Override - public String getUsageDescription() { - return "Resync updates dir"; - } - - @Override - public void invoke(String... args) throws IOException { - Set dirs = null; - if (args.length > 0) { // Hash all updates dirs - dirs = new HashSet<>(args.length); - Collections.addAll(dirs, args); - } - - // Hash updates dir - server.syncUpdatesDir(dirs); - logger.info("Updates dir successfully resynced"); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java index c180c504..167a63ca 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java @@ -8,8 +8,8 @@ import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider; -import pro.gravit.launchserver.auth.profiles.LocalProfileProvider; -import pro.gravit.launchserver.auth.profiles.ProfileProvider; +import pro.gravit.launchserver.auth.profiles.LocalProfilesProvider; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.StdProtectHandler; import pro.gravit.launchserver.auth.texture.RequestTextureProvider; @@ -39,7 +39,7 @@ public final class LaunchServerConfig { // Handlers & Providers public ProtectHandler protectHandler; public Map components; - public ProfileProvider profileProvider = new LocalProfileProvider(); + public ProfilesProvider profilesProvider = new LocalProfilesProvider(); public UpdatesProvider updatesProvider = new LocalUpdatesProvider(); public NettyConfig netty; public LauncherConf launcher; @@ -85,7 +85,8 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { newConfig.components.put("authLimiter", authLimiterComponent); ProGuardComponent proGuardComponent = new ProGuardComponent(); newConfig.components.put("proguard", proGuardComponent); - newConfig.profileProvider = new LocalProfileProvider(); + newConfig.profilesProvider = new LocalProfilesProvider(); + newConfig.updatesProvider = new LocalUpdatesProvider(); return newConfig; } @@ -167,9 +168,9 @@ public void init(LaunchServer.ReloadType type) { server.registerObject("protectHandler", protectHandler); protectHandler.init(server); } - if(profileProvider != null) { - server.registerObject("profileProvider", profileProvider); - profileProvider.init(server); + if(profilesProvider != null) { + server.registerObject("profileProvider", profilesProvider); + profilesProvider.init(server); } if(updatesProvider != null) { server.registerObject("updatesProvider", updatesProvider); @@ -215,9 +216,9 @@ public void close(LaunchServer.ReloadType type) { server.unregisterObject("protectHandler", protectHandler); protectHandler.close(); } - if(profileProvider != null) { - server.unregisterObject("profileProvider", profileProvider); - profileProvider.close(); + if(profilesProvider != null) { + server.unregisterObject("profilesProvider", profilesProvider); + profilesProvider.close(); } if(updatesProvider != null) { server.unregisterObject("updatesProvider", updatesProvider); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/AssetsDirHelper.java b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/AssetsDirHelper.java new file mode 100644 index 00000000..2aedd7d5 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/AssetsDirHelper.java @@ -0,0 +1,67 @@ +package pro.gravit.launchserver.helper; + +import com.google.gson.JsonObject; +import pro.gravit.launcher.base.Downloader; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launchserver.HttpRequester; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class AssetsDirHelper { + public static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json"; + public static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/"; + + public static List makeToDownloadFiles(AssetInfo assetInfo, HashedDir updatesDir) { + List toDownload = new ArrayList<>(128); + for (var e : assetInfo.assets.entrySet()) { + var value = e.getValue().getAsJsonObject(); + var hash = value.get("hash").getAsString(); + hash = hash.substring(0, 2) + "/" + hash; + var size = value.get("size").getAsLong(); + var path = "objects/" + hash; + if (updatesDir.tryFindRecursive(path).isFound()) { + continue; + } + toDownload.add(new Downloader.SizedFile(hash, path, size)); + } + return toDownload; + } + + public static AssetInfo getAssetInfo(HttpRequester requester, String versionName) throws IOException { + var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow(); + String profileUrl = null; + for (var e : versions.versions) { + if (e.id.equals(versionName)) { + profileUrl = e.url; + break; + } + } + var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow(); + String assetsIndexUrl = profileInfo.assetIndex.url; + String assetIndex = profileInfo.assetIndex.id; + JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow(); + return new AssetInfo(assetIndex, assets); + } + + public record AssetInfo(String assetIndex, JsonObject assets) { + + } + + public record MiniVersionInfo(String id, String url) { + + } + + public record MinecraftVersions(List versions) { + + } + + public record MinecraftAssetIndexInfo(String id, String url) { + + } + + public record MiniVersion(MinecraftAssetIndexInfo assetIndex) { + + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index fcfb4b20..ab15acd1 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -191,7 +191,7 @@ public PlayerProfile getPlayerProfile(Client client) { playerProfile = getPlayerProfile(client.auth, user); if (playerProfile != null) return playerProfile; if (client.auth.textureProvider != null) { - return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getTitle(), client.auth.textureProvider, new HashMap<>()); + return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getName(), client.auth.textureProvider, new HashMap<>()); } // Return combined profile return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>()); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java index 4970e4f0..655457b0 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java @@ -14,7 +14,7 @@ import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.password.PasswordVerifier; -import pro.gravit.launchserver.auth.profiles.ProfileProvider; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.updates.UpdatesProvider; @@ -48,7 +48,7 @@ public void registerAdapters(GsonBuilder builder) { builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers)); builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers)); builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers)); - builder.registerTypeAdapter(ProfileProvider.class, new UniversalJsonAdapter<>(ProfileProvider.providers)); + builder.registerTypeAdapter(ProfilesProvider.class, new UniversalJsonAdapter<>(ProfilesProvider.providers)); builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers)); modulesManager.invokeEvent(new PreGsonPhase(builder)); //ClientWebSocketService.appendTypeAdapters(builder); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/UpdatesManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/UpdatesManager.java deleted file mode 100644 index bd86fe0b..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/UpdatesManager.java +++ /dev/null @@ -1,45 +0,0 @@ -package pro.gravit.launchserver.manangers; - -import pro.gravit.launcher.core.hasher.HashedDir; -import pro.gravit.launchserver.LaunchServer; - -import java.io.IOException; -import java.util.*; - -public class UpdatesManager { - private final LaunchServer server; - - public UpdatesManager(LaunchServer server) { - this.server = server; - } - - @Deprecated - public void readUpdatesFromCache() { - - } - - @Deprecated - public void readUpdatesDir() { - - } - - @Deprecated - public void syncUpdatesDir(Collection dirs) throws IOException { - server.config.updatesProvider.sync(dirs); - } - - @Deprecated - public HashSet getUpdatesList() { - return new HashSet<>(); - } - - @Deprecated - public HashedDir getUpdate(String name) { - return server.config.updatesProvider.getUpdatesDir(name); - } - - @Deprecated - public void addUpdate(String name, HashedDir dir) { - throw new UnsupportedOperationException(); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java index 7ef57961..b44418f2 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java @@ -5,6 +5,7 @@ import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.core.interfaces.UserHardware; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.socket.response.auth.AuthResponse; import java.util.HashMap; @@ -19,7 +20,7 @@ public class Client { public String auth_id; public long timestamp; public AuthResponse.ConnectTypes type; - public ClientProfile profile; + public ProfilesProvider.CompletedProfile profile; public boolean isAuth; public boolean checkSign; public ClientPermissions permissions; diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ProfilesResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ProfilesResponse.java index 6e5be16a..de888a1c 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ProfilesResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ProfilesResponse.java @@ -3,8 +3,9 @@ import io.netty.channel.ChannelHandlerContext; import pro.gravit.launcher.base.events.request.ProfilesRequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.SimpleResponse; @@ -13,21 +14,22 @@ import java.util.Set; public class ProfilesResponse extends SimpleResponse { - @Deprecated + public static List getListVisibleProfiles(LaunchServer server, Client client) { - List profileList; - Set serverProfiles = server.getProfiles(); - if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) { - profileList = new ArrayList<>(4); - for (ClientProfile profile : serverProfiles) { - if (protectHandler.canGetProfile(profile, client)) { - profileList.add(profile); - } + Set serverProfiles = server.config.profilesProvider.getProfiles(client); + List profiles = new ArrayList<>(); + for(var uncompleted : serverProfiles) { + if(uncompleted instanceof ProfilesProvider.CompletedProfile completed) { + profiles.add(completed.getProfile()); + } else { + profiles.add(new ClientProfileBuilder() + .setUuid(uncompleted.getUuid()) + .setTitle(uncompleted.getName()) + .setInfo(uncompleted.getDescription()) + .createClientProfile()); } - } else { - profileList = List.copyOf(serverProfiles); } - return profileList; + return profiles; } @Override @@ -37,10 +39,6 @@ public String getType() { @Override public void execute(ChannelHandlerContext ctx, Client client) { - if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetProfiles(client)) { - sendError("Access denied"); - return; - } - sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client))); + sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client))); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java index b6cb4de1..814a6c28 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java @@ -2,16 +2,15 @@ import io.netty.channel.ChannelHandlerContext; import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; -import pro.gravit.launcher.base.profiles.ClientProfile; -import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.utils.HookException; -import java.util.Collection; +import java.util.UUID; public class SetProfileResponse extends SimpleResponse { - public String client; + public UUID uuid; + public String tag; @Override public String getType() { @@ -25,20 +24,13 @@ public void execute(ChannelHandlerContext ctx, Client client) { } catch (HookException e) { sendError(e.getMessage()); } - Collection profiles = server.getProfiles(); - for (ClientProfile p : profiles) { - if (p.getTitle().equals(this.client)) { - if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && - !profilesProtectHandler.canChangeProfile(p, client)) { - sendError("Access denied"); - return; - } - client.profile = p; - sendResult(new SetProfileRequestEvent(p)); - return; - } + var profile = server.config.profilesProvider.get(uuid, tag); + if(profile == null) { + sendError("Profile not found"); + return; } - sendError("Profile not found"); + client.profile = profile; + sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag())); } @Override diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/LauncherResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/LauncherResponse.java index 1e2285f1..5cc946d7 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/LauncherResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/LauncherResponse.java @@ -9,6 +9,7 @@ import pro.gravit.launcher.base.events.request.LauncherRequestEvent; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; +import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse; @@ -43,28 +44,21 @@ public void execute(ChannelHandlerContext ctx, Client client) { bytes = Base64.getDecoder().decode(hash); else bytes = digest; - if (launcher_type == 1) // JAR - { - byte[] hash = server.launcherBinary.getDigest(); - if (hash == null) - service.sendObjectAndClose(ctx, new LauncherRequestEvent(true, server.config.netty.launcherURL)); - if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) { - client.checkSign = true; - sendResult(new LauncherRequestEvent(false, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000)); - } else { - sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, null, 0)); - } - } else if (launcher_type == 2) //EXE - { - byte[] hash = server.launcherEXEBinary.getDigest(); - if (hash == null) sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL)); - if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) { - client.checkSign = true; - sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000)); - } else { - sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0)); - } - } else sendError("Request launcher type error"); + UpdatesProvider.UpdateVariant variant = UpdatesProvider.UpdateVariant.JAR; + if(launcher_type == 2) { + variant = UpdatesProvider.UpdateVariant.EXE; + } + byte[] hashToCheck = bytes; + if(!checkSecure(secureHash, secureSalt)) { + hashToCheck = null; // Always need update + } + UpdatesProvider.UpdateInfo info = server.config.updatesProvider.checkUpdates(variant, hashToCheck); + if (info != null) { + sendResult(new LauncherRequestEvent(true, info.url())); + } else { + client.checkSign = true; + sendResult(new LauncherRequestEvent(false, null, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000)); + } } public String createLauncherExtendedToken() { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/UpdateResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/UpdateResponse.java index 9f92517d..1b0d4e1b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/UpdateResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/update/UpdateResponse.java @@ -3,7 +3,6 @@ import io.netty.channel.ChannelHandlerContext; import pro.gravit.launcher.base.events.request.UpdateRequestEvent; import pro.gravit.launcher.core.hasher.HashedDir; -import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.SimpleResponse; @@ -19,15 +18,22 @@ public String getType() { @Override public void execute(ChannelHandlerContext ctx, Client client) { - if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetUpdates(dirName, client)) { - sendError("Access denied"); - return; - } if (dirName == null) { sendError("Invalid request"); return; } - HashedDir dir = server.updatesManager.getUpdate(dirName); + if(client.profile == null) { + sendError("Profile not setted"); + return; + } + HashedDir dir = null; + if(dirName.equals(client.profile.getProfile().getDir())) { + dir = client.profile.getClientDir(); + } else if(dirName.equals(client.profile.getProfile().getAssetDir())) { + dir = client.profile.getAssetDir(); + } else { + dir = server.config.profilesProvider.getUnconnectedDirectory(dirName); + } if (dir == null) { sendError("Directory %s not found".formatted(dirName)); return; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java index b3a92dbb..320862dc 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java @@ -12,9 +12,17 @@ public class SetProfileRequestEvent extends RequestEvent { private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541"); @LauncherNetworkAPI public final ClientProfile newProfile; + @LauncherNetworkAPI + public final String tag; public SetProfileRequestEvent(ClientProfile newProfile) { this.newProfile = newProfile; + this.tag = null; + } + + public SetProfileRequestEvent(ClientProfile newProfile, String tag) { + this.newProfile = newProfile; + this.tag = tag; } @Override diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/SetProfileRequest.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/SetProfileRequest.java index 8e99693f..b5bfc8e1 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/SetProfileRequest.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/SetProfileRequest.java @@ -6,12 +6,21 @@ import pro.gravit.launcher.base.request.Request; import pro.gravit.launcher.base.request.websockets.WebSocketRequest; +import java.util.UUID; + public class SetProfileRequest extends Request implements WebSocketRequest { @LauncherNetworkAPI - public final String client; + public final UUID uuid; + public final String tag; + + public SetProfileRequest(UUID uuid, String tag) { + this.uuid = uuid; + this.tag = tag; + } public SetProfileRequest(ClientProfile profile) { - this.client = profile.getTitle(); + this.uuid = profile.getUUID(); + this.tag = null; } @Override diff --git a/modules b/modules index 38788df6..6f699fae 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 38788df61f8efc5453fe9c05017b629648b5fe6f +Subproject commit 6f699fae50f98cec19279092d9d5fac1ac451914