diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java index 456136e3..010e466b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java @@ -152,12 +152,8 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti } } } - builder.setMinJavaVersion(17); - builder.setRecommendJavaVersion(17); - if(version.compareTo(ClientProfileVersions.MINECRAFT_1_20_3) >= 0) { - builder.setMinJavaVersion(21); - builder.setRecommendJavaVersion(21); - } + builder.setMinJavaVersion(21); + builder.setRecommendJavaVersion(21); jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true"); jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); builder.setJvmArgs(jvmArgs); diff --git a/LaunchServer/src/main/resources/experimental-build.json b/LaunchServer/src/main/resources/experimental-build.json index ab148d00..8f5e3d65 100644 --- a/LaunchServer/src/main/resources/experimental-build.json +++ b/LaunchServer/src/main/resources/experimental-build.json @@ -1,4 +1,4 @@ { - "features": [], + "features": ["new-api"], "info": [] } \ No newline at end of file diff --git a/Launcher/build.gradle b/Launcher/build.gradle index 42ed13a7..2852258a 100644 --- a/Launcher/build.gradle +++ b/Launcher/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'com.gradleup.shadow' String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper" @@ -7,8 +7,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' configurations { bundle diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java index df1e873b..a752b47b 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java @@ -2,7 +2,15 @@ import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.LauncherConfig; +import pro.gravit.launcher.base.request.*; import pro.gravit.launcher.client.*; +import pro.gravit.launcher.core.api.LauncherAPI; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPIHolder; +import pro.gravit.launcher.runtime.backend.LauncherBackendImpl; import pro.gravit.launcher.runtime.client.*; import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase; import pro.gravit.launcher.client.events.ClientExitPhase; @@ -19,9 +27,6 @@ import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; -import pro.gravit.launcher.base.request.Request; -import pro.gravit.launcher.base.request.RequestException; -import pro.gravit.launcher.base.request.RequestService; import pro.gravit.launcher.base.request.auth.*; import pro.gravit.launcher.base.request.websockets.OfflineRequestService; import pro.gravit.launcher.base.request.websockets.StdWebSocketService; @@ -38,6 +43,7 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -243,6 +249,17 @@ public void start(String... args) throws Throwable { } Request.startAutoRefresh(); Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler()); + // Init New API + LauncherAPIHolder.setCoreAPI(new RequestCoreFeatureAPIImpl(Request.getRequestService())); + LauncherAPIHolder.setCreateApiFactory((authId) -> { + var impl = new RequestFeatureAPIImpl(Request.getRequestService(), authId); + return new LauncherAPI(Map.of( + AuthFeatureAPI.class, impl, + UserFeatureAPI.class, impl, + ProfileFeatureAPI.class, impl)); + }); + LauncherBackendAPIHolder.setApi(new LauncherBackendImpl()); + // Objects.requireNonNull(args, "args"); if (started.getAndSet(true)) throw new IllegalStateException("Launcher has been already started"); diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java index a58108ac..b29f1b5d 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java @@ -1,6 +1,6 @@ package pro.gravit.launcher.runtime; -import pro.gravit.launcher.runtime.client.UserSettings; +import pro.gravit.launcher.core.backend.UserSettings; import pro.gravit.launcher.core.LauncherNetworkAPI; import java.util.HashMap; diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java new file mode 100644 index 00000000..c42a2837 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java @@ -0,0 +1,43 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.backend.UserSettings; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class BackendSettings extends UserSettings { + @LauncherNetworkAPI + public AuthorizationData auth; + @LauncherNetworkAPI + public Map settings = new HashMap<>(); + public static class AuthorizationData { + @LauncherNetworkAPI + public String accessToken; + @LauncherNetworkAPI + public String refreshToken; + @LauncherNetworkAPI + public long expireIn; + + public AuthFeatureAPI.AuthToken toToken() { + return new AuthFeatureAPI.AuthToken() { + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public String getRefreshToken() { + return refreshToken; + } + + @Override + public long getExpire() { + return expireIn; + } + }; + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java new file mode 100644 index 00000000..f52f6a2b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java @@ -0,0 +1,263 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Downloader; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.optional.OptionalView; +import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; +import pro.gravit.launcher.base.profiles.optional.actions.OptionalActionFile; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.core.hasher.FileNameMatcher; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launcher.core.hasher.HashedEntry; +import pro.gravit.launcher.core.hasher.HashedFile; +import pro.gravit.launcher.runtime.client.DirBridge; +import pro.gravit.launcher.runtime.utils.AssetIndexHelper; +import pro.gravit.utils.helper.LogHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +public class ClientDownloadImpl { + private LauncherBackendImpl backend; + + ClientDownloadImpl(LauncherBackendImpl backend) { + this.backend = backend; + } + + CompletableFuture downloadProfile(ClientProfile profile, ProfileSettingsImpl settings, LauncherBackendAPI.DownloadCallback callback) { + AtomicReference clientRef = new AtomicReference<>(); + AtomicReference assetRef = new AtomicReference<>(); + AtomicReference javaRef = new AtomicReference<>(); + return LauncherAPIHolder.profile().changeCurrentProfile(profile) + .thenCompose(vv -> downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback)).thenCompose((clientDir -> { + clientRef.set(clientDir); + return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback); + })).thenCompose(assetDir -> { + assetRef.set(assetDir); + return CompletableFuture.completedFuture((DownloadedDir)null); // TODO Custom Java + }).thenCompose(javaDir -> { + javaRef.set(javaDir); + return CompletableFuture.completedFuture(null); + }).thenApply(v -> { + return new ReadyProfileImpl(backend, profile, settings, clientRef.get(), assetRef.get(), javaRef.get()); + }); + } + + CompletableFuture downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString+".json"); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> { + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY); + return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir); + }, backend.executorService) + .thenApply(assetData -> { + HashedDir dir = assetData.updateInfo.getHashedDir(); + AssetIndexHelper.modifyHashedDir(assetData.index, dir); + return new VirtualUpdateInfo(dir, assetData.updateInfo.getUrl()); + }) + .thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e)); + } + + private CompletableFuture verifyAssetIndex(String assetIndexString, ProfileFeatureAPI.UpdateInfo response, Path assetIndexPath, Path targetDir) { + var assetIndexRelPath = String.format("indexes/%s.json", assetIndexString); + var assetIndexHash = response.getHashedDir().findRecursive(assetIndexRelPath); + if(!(assetIndexHash.entry instanceof HashedFile assetIndexHashFile)) { + return CompletableFuture.failedFuture(new FileNotFoundException(String.format("Asset Index %s not found in the server response", assetIndexString))); + } + try { + if(Files.exists(assetIndexPath) && assetIndexHashFile.isSame(assetIndexPath, true)) { + var assetIndex = AssetIndexHelper.parse(assetIndexPath); + return CompletableFuture.completedFuture(new AssetData(response, assetIndex)); + } else { + var downloader = Downloader.newDownloader(backend.executorService); + var list = new LinkedList(); + list.add(new Downloader.SizedFile(assetIndexRelPath, assetIndexRelPath, assetIndexHashFile.size)); + return downloader.downloadFiles(list, response.getUrl(), targetDir, null, backend.executorService, 1).thenComposeAsync(v -> { + try { + var assetIndex = AssetIndexHelper.parse(assetIndexPath); + return CompletableFuture.completedFuture(new AssetData(response, assetIndex)); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } + }, backend.executorService); + } + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + CompletableFuture downloadDir(String dirName, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName) + .thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e)); + } + + CompletableFuture downloadDir(String dirName, FileNameMatcher matcher, OptionalView view, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName) + .thenCompose(response -> { + var hashedDir = response.getHashedDir(); + var remap = applyOptionalMods(view, hashedDir); + return downloadDir(targetDir, new VirtualUpdateInfo(hashedDir, response.getUrl()), matcher, callback, makePathRemapperFunction(remap)); + }); + } + + CompletableFuture downloadDir(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback, Function remap) { + return CompletableFuture.supplyAsync(() -> { + try { + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING); + HashedDir realFiles = new HashedDir(targetDir, matcher, false, true); + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF); + return updateInfo.getHashedDir().diff(realFiles, matcher); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, backend.executorService).thenComposeAsync((diff) -> { + return downloadFiles(targetDir, updateInfo, callback, diff, remap); + }, backend.executorService).thenApply(v -> new DownloadedDir(updateInfo.getHashedDir(), targetDir)); + } + + private CompletableFuture downloadFiles(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, LauncherBackendAPI.DownloadCallback callback, HashedDir.Diff diff, Function remap) { + Downloader downloader = Downloader.newDownloader(backend.executorService); + try { + var files = collectFilesAndCreateDirectories(targetDir, diff.mismatch, remap); + long total = 0; + for(var e : files) { + total += e.size; + } + callback.onTotalDownload(total); + callback.onCanCancel(downloader::cancel); + return downloader.downloadFiles(files, updateInfo.getUrl(), targetDir, new Downloader.DownloadCallback() { + @Override + public void apply(long fullDiff) { + callback.onCurrentDownloaded(fullDiff); + } + + @Override + public void onComplete(Path path) { + + } + }, backend.executorService, 4).thenComposeAsync(v -> { + callback.onCanCancel(null); + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DELETE_EXTRA); + try { + deleteExtraDir(targetDir, diff.extra, diff.extra.flag); + } catch (IOException ex) { + return CompletableFuture.failedFuture(ex); + } + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DONE_PART); + return CompletableFuture.completedFuture(diff); + }, backend.executorService); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + private List collectFilesAndCreateDirectories(Path dir, HashedDir mismatch, Function pathRemapper) throws IOException { + List files = new ArrayList<>(); + mismatch.walk(File.separator, (path, name, entry) -> { + if(entry.getType() == HashedEntry.Type.DIR) { + var dirPath = dir.resolve(path); + try { + if(!Files.exists(dirPath)) { + Files.createDirectory(dirPath); + } else if (!Files.isDirectory(dirPath)) { + throw new IOException(String.format("%s is not a directory", path)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return HashedDir.WalkAction.CONTINUE; + } + String pathFixed = path.replace(File.separatorChar, '/'); + files.add(new Downloader.SizedFile(pathFixed, pathRemapper.apply(pathFixed), entry.size())); + return HashedDir.WalkAction.CONTINUE; + }); + return files; + } + + private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean deleteDir) throws IOException { + for (Map.Entry mapEntry : subHDir.map().entrySet()) { + String name = mapEntry.getKey(); + Path path = subDir.resolve(name); + + // Delete list and dirs based on type + HashedEntry entry = mapEntry.getValue(); + HashedEntry.Type entryType = entry.getType(); + switch (entryType) { + case FILE -> Files.delete(path); + case DIR -> deleteExtraDir(path, (HashedDir) entry, deleteDir || entry.flag); + default -> throw new AssertionError("Unsupported hashed entry type: " + entryType.name()); + } + } + + // Delete! + if (deleteDir) { + Files.delete(subDir); + } + } + + private Function makePathRemapperFunction(LinkedList map) { + return (path) -> { + for(var e : map) { + if(path.startsWith(e.key)) { + return e.value; + } + } + return path; + }; + } + + private LinkedList applyOptionalMods(OptionalView view, HashedDir hdir) { + for (OptionalAction action : view.getDisabledActions()) { + if (action instanceof OptionalActionFile optionalActionFile) { + optionalActionFile.disableInHashedDir(hdir); + } + } + LinkedList pathRemapper = new LinkedList<>(); + Set fileActions = view.getActionsByClass(OptionalActionFile.class); + for (OptionalActionFile file : fileActions) { + file.injectToHashedDir(hdir); + file.files.forEach((k, v) -> { + if (v == null || v.isEmpty()) return; + pathRemapper.add(new PathRemapperData(v, k)); //reverse (!) + LogHelper.dev("Remap prepare %s to %s", v, k); + }); + } + pathRemapper.sort(Comparator.comparingInt(c -> -c.key.length())); // Support deep remap + return pathRemapper; + } + + private record PathRemapperData(String key, String value) { + } + + record AssetData(ProfileFeatureAPI.UpdateInfo updateInfo, AssetIndexHelper.AssetIndex index) { + + } + + record DownloadedDir(HashedDir dir, Path path) { + + } + + record VirtualUpdateInfo(HashedDir dir, String url) implements ProfileFeatureAPI.UpdateInfo { + + @Override + public HashedDir getHashedDir() { + return dir; + } + + @Override + public String getUrl() { + return url; + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java new file mode 100644 index 00000000..a8191c9b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java @@ -0,0 +1,304 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.ClientPermissions; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.CoreFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.Texture; +import pro.gravit.launcher.core.api.model.UserPermissions; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.core.backend.UserSettings; +import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException; +import pro.gravit.launcher.core.backend.extensions.Extension; +import pro.gravit.launcher.runtime.NewLauncherSettings; +import pro.gravit.launcher.runtime.client.DirBridge; +import pro.gravit.launcher.runtime.client.ServerPinger; +import pro.gravit.launcher.runtime.debug.DebugMain; +import pro.gravit.launcher.runtime.managers.SettingsManager; +import pro.gravit.launcher.runtime.utils.LauncherUpdater; +import pro.gravit.utils.helper.JavaHelper; +import pro.gravit.utils.helper.LogHelper; + +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; + +public class LauncherBackendImpl implements LauncherBackendAPI { + private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this); + private volatile MainCallback callback; + ExecutorService executorService; + private volatile AuthMethod authMethod; + // Settings + private SettingsManager settingsManager; + private NewLauncherSettings allSettings; + private BackendSettings backendSettings; + // Data + private volatile List profiles; + private volatile UserPermissions permissions; + private volatile SelfUser selfUser; + private volatile List availableJavas; + private volatile CompletableFuture> availableJavasFuture; + private final Map> pingFutures = new ConcurrentHashMap<>(); + + @Override + public void setCallback(MainCallback callback) { + this.callback = callback; + } + + private void doInit() throws Exception { + executorService = Executors.newScheduledThreadPool(2, (r) -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + }); + registerUserSettings("backend", BackendSettings.class); + settingsManager = new SettingsManager(); + settingsManager.generateConfigIfNotExists(); + settingsManager.loadConfig(); + allSettings = settingsManager.getConfig(); + backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings()); + permissions = new ClientPermissions(); + DirBridge.dirUpdates = DirBridge.defaultUpdatesDir; + } + + @Override + public CompletableFuture init() { + try { + doInit(); + } catch (Throwable e) { + return CompletableFuture.failedFuture(e); + } + CompletableFuture feature; + if(isTestMode()) { + feature = CompletableFuture.completedFuture(new CoreFeatureAPI.LauncherUpdateInfo(null, "Unknown", false, false)); + } else { + feature = LauncherAPIHolder.core().checkUpdates(); + } + return feature.thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> { + if(updatesInfo.required()) { + try { + LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL()); + } catch (Exception e) { + throw new RuntimeException(e); + } + callback.onShutdown(); + LauncherUpdater.restart(); + } + return new LauncherInitData(authMethods); + }, executorService); + } + + public AuthFeatureAPI.AuthToken getAuthToken() { + return backendSettings.auth.toToken(); + } + + public AuthMethod getAuthMethod() { + return authMethod; + } + + @Override + public void selectAuthMethod(AuthMethod method) { + this.authMethod = method; + LauncherAPIHolder.changeAuthId(method.getName()); + } + + @Override + public CompletableFuture tryAuthorize() { + if(this.authMethod == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod")); + } + if(backendSettings.auth == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("Auth data not found")); + } + if(backendSettings.auth.expireIn > 0 && LocalDateTime.ofInstant(Instant.ofEpochMilli(backendSettings.auth.expireIn), ZoneOffset.UTC).isBefore(LocalDateTime.now(ZoneOffset.UTC))) { + return LauncherAPIHolder.auth().refreshToken(backendSettings.auth.refreshToken).thenCompose((response) -> { + setAuthToken(response); + return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true); + }).thenApply((user) -> { + onAuthorize(user); + return user; + }); + } + return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true).thenApply((user) -> { + onAuthorize(user); + return user; + }); + } + + private void setAuthToken(AuthFeatureAPI.AuthToken authToken) { + backendSettings.auth = new BackendSettings.AuthorizationData(); + backendSettings.auth.accessToken = authToken.getAccessToken(); + backendSettings.auth.refreshToken = authToken.getRefreshToken(); + if(authToken.getExpire() <= 0) { + backendSettings.auth.expireIn = 0; + } + backendSettings.auth.expireIn = LocalDateTime.now(ZoneOffset.UTC) + .plus(authToken.getExpire(), ChronoUnit.MILLIS). + toEpochSecond(ZoneOffset.UTC); + } + + private void onAuthorize(SelfUser selfUser) { + this.selfUser = selfUser; + permissions = selfUser.getPermissions(); + callback.onAuthorize(selfUser); + } + + @Override + public CompletableFuture authorize(String login, AuthMethodPassword password) { + if(this.authMethod == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod")); + } + return LauncherAPIHolder.auth().auth(login, password).thenApply((response) -> { + setAuthToken(response.authToken()); + onAuthorize(response.user()); + return response.user(); + }); + } + + @Override + public CompletableFuture> fetchProfiles() { + return LauncherAPIHolder.profile().getProfiles().thenApply((profiles) -> { + this.profiles = profiles; + callback.onProfiles(profiles); + return profiles; + }); + } + + @Override + public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile) { + var settings = backendSettings.settings.get(profile.getUUID()); + if(settings == null) { + settings = new ProfileSettingsImpl((ClientProfile) profile); + settings.backend = this; + settings.updateEnabledMods(); + } else { + settings = settings.copy(); + //settings.initAfterGson((ClientProfile) profile, this); + } + return settings; + } + + @Override + public void saveClientProfileSettings(ClientProfileSettings settings) { + var impl = (ProfileSettingsImpl) settings; + impl.updateEnabledMods(); + backendSettings.settings.put(impl.profile.getUUID(), impl); + } + + @Override + public CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback) { + return clientDownloadImpl.downloadProfile((ClientProfile) profile, (ProfileSettingsImpl) settings, callback); + } + + @Override + public CompletableFuture fetchTexture(Texture texture) { + return CompletableFuture.failedFuture(new UnsupportedOperationException()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getAvailableJava() { + if(availableJavas == null) { + if(availableJavasFuture == null) { + availableJavasFuture = CompletableFuture.supplyAsync(() -> { + return (List) JavaHelper.findJava(); // TODO: Custom Java + }, executorService).thenApply(e -> { + availableJavas = e; + return e; + }); + } + return availableJavasFuture; + } + return CompletableFuture.completedFuture(availableJavas); + } + + @Override + public CompletableFuture pingServer(ProfileFeatureAPI.ClientProfile profile) { + return pingFutures.computeIfAbsent(profile.getUUID(), (k) -> { + CompletableFuture future = new CompletableFuture<>(); + executorService.submit(() -> { + try { + ServerPinger pinger = new ServerPinger((ClientProfile) profile); + future.complete(pinger.ping()); + } catch (Throwable e) { + future.completeExceptionally(e); + } + }); + return future; + }); + } + + @Override + public void registerUserSettings(String name, Class clazz) { + UserSettings.providers.register(name, clazz); + } + + @Override + public UserSettings getUserSettings(String name, Function ifNotExist) { + return allSettings.userSettings.computeIfAbsent(name, ifNotExist); + } + + @Override + public UserPermissions getPermissions() { + return permissions; + } + + @Override + public boolean hasPermission(String permission) { + return permissions.hasPerm(permission); + } + + @Override + public String getUsername() { + return selfUser == null ? "Player" : getUsername(); + } + + @Override + public SelfUser getSelfUser() { + return selfUser; + } + + @Override + public boolean isTestMode() { + try { + return DebugMain.IS_DEBUG.get(); + } catch (Throwable ex) { + return false; + } + } + + @Override + public T getExtension(Class clazz) { + return null; + } + + @Override + public void shutdown() { + if(executorService != null) { + executorService.shutdownNow(); + } + if(settingsManager != null) { + try { + settingsManager.saveConfig(); + } catch (IOException e) { + LogHelper.error("Config not saved", e); + } + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java new file mode 100644 index 00000000..cd175ec4 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -0,0 +1,201 @@ +package pro.gravit.launcher.runtime.backend; + +import oshi.SystemInfo; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.optional.OptionalFile; +import pro.gravit.launcher.base.profiles.optional.OptionalView; +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.runtime.utils.SystemMemory; +import pro.gravit.utils.helper.JVMHelper; +import pro.gravit.utils.helper.JavaHelper; + +import java.util.*; +import java.util.concurrent.ExecutionException; + +public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings { + transient ClientProfile profile; + transient LauncherBackendImpl backend; + @LauncherNetworkAPI + private Map ram; + @LauncherNetworkAPI + private Set flags; + @LauncherNetworkAPI + private Set enabled; + @LauncherNetworkAPI + private String saveJavaPath; + transient OptionalView view; + transient JavaHelper.JavaVersion selectedJava; + + public ProfileSettingsImpl() { + } + + public ProfileSettingsImpl(ClientProfile profile) { + this.profile = profile; + var def = profile.getSettings(); + this.ram = new HashMap<>(); + this.ram.put(MemoryClass.TOTAL, ((long)def.ram) << 20); + this.flags = new HashSet<>(); + if(def.autoEnter) { + this.flags.add(Flag.AUTO_ENTER); + } + if(def.fullScreen) { + this.flags.add(Flag.FULLSCREEN); + } + this.view = new OptionalView(profile); + } + + @Override + public long getReservedMemoryBytes(MemoryClass memoryClass) { + return ram.getOrDefault(memoryClass, 0L); + } + + @Override + public long getMaxMemoryBytes(MemoryClass memoryClass) { + try { + return SystemMemory.getPhysicalMemorySize(); + } catch (Throwable e) { + SystemInfo systemInfo = new SystemInfo(); + return systemInfo.getHardware().getMemory().getTotal(); + } + } + + @Override + public void setReservedMemoryBytes(MemoryClass memoryClass, long value) { + this.ram.put(memoryClass, value); + } + + @Override + public Set getFlags() { + return Collections.unmodifiableSet(flags); + } + + @Override + public Set getAvailableFlags() { + Set set = new HashSet<>(); + set.add(Flag.AUTO_ENTER); + set.add(Flag.FULLSCREEN); + if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { + set.add(Flag.LINUX_WAYLAND_SUPPORT); + } + if(backend.hasPermission("launcher.debug.skipfilemonitor")) { + set.add(Flag.DEBUG_SKIP_FILE_MONITOR); + } + return set; + } + + @Override + public boolean hasFlag(Flag flag) { + return flags.contains(flag); + } + + @Override + public void addFlag(Flag flag) { + flags.add(flag); + } + + @Override + public void removeFlag(Flag flag) { + flags.remove(flag); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public Set getEnabledOptionals() { + return (Set) view.enabled; + } + + @Override + public void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) { + view.enable((OptionalFile) mod, true, callback::onChanged); + } + + @Override + public void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) { + view.disable((OptionalFile) mod, callback::onChanged); + } + + @Override + public JavaHelper.JavaVersion getSelectedJava() { + return selectedJava; + } + + @Override + public JavaHelper.JavaVersion getRecommendedJava() { + JavaHelper.JavaVersion result = null; + try { + for(var java : backend.getAvailableJava().get()) { + if(isRecommended(java)) { + return (JavaHelper.JavaVersion) java; + } + if(isCompatible(java)) { + if(result == null) { + result = (JavaHelper.JavaVersion) java; + continue; + } + if(result.getMajorVersion() < java.getMajorVersion()) { + result = (JavaHelper.JavaVersion) java; + } + } + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return result; + } + + @Override + public void setSelectedJava(LauncherBackendAPI.Java java) { + selectedJava = (JavaHelper.JavaVersion) java; + } + + @Override + public boolean isRecommended(LauncherBackendAPI.Java java) { + return java.getMajorVersion() == profile.getRecommendJavaVersion(); + } + + @Override + public boolean isCompatible(LauncherBackendAPI.Java java) { + return java.getMajorVersion() >= profile.getMinJavaVersion() && java.getMajorVersion() <= profile.getMaxJavaVersion(); + } + + @Override + public ProfileSettingsImpl copy() { + ProfileSettingsImpl cloned = new ProfileSettingsImpl(); + cloned.backend = backend; + cloned.profile = profile; + cloned.ram = new HashMap<>(ram); + cloned.flags = new HashSet<>(flags); + cloned.enabled = new HashSet<>(enabled); + if(view != null) { + cloned.view = new OptionalView(profile, view); + } + cloned.selectedJava = selectedJava; + cloned.saveJavaPath = saveJavaPath; + return cloned; + } + + public void updateEnabledMods() { + enabled = new HashSet<>(); + for(var e : view.enabled) { + enabled.add(e.name); + } + if(selectedJava != null) { + saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); + } + } + + public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { + this.backend = backend; + this.profile = profile; + this.view = new OptionalView(profile); + for(var e : enabled) { + var opt = profile.getOptionalFile(e); + if(opt == null) { + continue; + } + enableOptional(opt, (var1, var2) -> {}); + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java new file mode 100644 index 00000000..c3092161 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java @@ -0,0 +1,135 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.events.request.AuthRequestEvent; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; +import pro.gravit.launcher.base.profiles.PlayerProfile; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.runtime.client.ClientLauncherProcess; +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JVMHelper; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.ArrayList; + +public class ReadyProfileImpl implements LauncherBackendAPI.ReadyProfile { + private LauncherBackendImpl backend; + private ClientProfile profile; + private ProfileSettingsImpl settings; + private ClientDownloadImpl.DownloadedDir clientDir; + private ClientDownloadImpl.DownloadedDir assetDir; + private ClientDownloadImpl.DownloadedDir javaDir; + private volatile Thread writeParamsThread; + private volatile Thread runThread; + private volatile ClientLauncherProcess process; + private volatile Process nativeProcess; + private volatile LauncherBackendAPI.RunCallback callback; + + public ReadyProfileImpl(LauncherBackendImpl backend, ClientProfile profile, ProfileSettingsImpl settings, ClientDownloadImpl.DownloadedDir clientDir, ClientDownloadImpl.DownloadedDir assetDir, ClientDownloadImpl.DownloadedDir javaDir) { + this.backend = backend; + this.profile = profile; + this.settings = settings; + this.clientDir = clientDir; + this.assetDir = assetDir; + this.javaDir = javaDir; + } + + @Override + public ProfileFeatureAPI.ClientProfile getClientProfile() { + return profile; + } + + @Override + public LauncherBackendAPI.ClientProfileSettings getSettings() { + return settings; + } + + @Override + public void run(LauncherBackendAPI.RunCallback callback) { + if(isAlive()) { + terminate(); + } + this.callback = callback; + if(backend.hasPermission("launcher.debug.skipfilemonitor") && settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.DEBUG_SKIP_FILE_MONITOR)) { + var builder = new ClientProfileBuilder(profile); + builder.setUpdate(new ArrayList<>()); + builder.setUpdateVerify(new ArrayList<>()); + builder.setUpdateExclusions(new ArrayList<>()); + profile = builder.createClientProfile(); + } + var java = settings.getSelectedJava(); + if(java == null) { + java = settings.getRecommendedJava(); + } + process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), java, clientDir.path().resolve("resourcepacks"), + profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(), + clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir(), + new AuthRequestEvent.OAuthRequestEvent(backend.getAuthToken()), backend.getAuthMethod().getName()); + process.params.ram = (int) (settings.getReservedMemoryBytes(LauncherBackendAPI.ClientProfileSettings.MemoryClass.TOTAL) >> 20); + if (process.params.ram > 0) { + process.jvmArgs.add("-Xms" + process.params.ram + 'M'); + process.jvmArgs.add("-Xmx" + process.params.ram + 'M'); + } + process.params.fullScreen = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.FULLSCREEN); + process.params.autoEnter = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.AUTO_ENTER); + if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { + process.params.lwjglGlfwWayland = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.LINUX_WAYLAND_SUPPORT); + } + writeParamsThread = new Thread(this::writeParams); + writeParamsThread.setDaemon(true); + writeParamsThread.start(); + runThread = new Thread(this::readThread); + runThread.setDaemon(true); + runThread.start(); + } + + private void readThread() { + try { + process.start(true); + nativeProcess = process.getProcess(); + callback.onCanTerminate(this::terminate); + InputStream stream = nativeProcess.getInputStream(); + byte[] buf = IOHelper.newBuffer(); + try { + for (int length = stream.read(buf); length >= 0; length = stream.read(buf)) { + callback.onNormalOutput(buf, 0, length); + } + } catch (EOFException ignored) { + } + if (nativeProcess.isAlive()) { + int code = nativeProcess.waitFor(); + callback.onFinished(code); + } + } catch (Exception e) { + if(e instanceof InterruptedException) { + return; + } + terminate(); + } + } + + public void terminate() { + if(nativeProcess == null) { + return; + } + nativeProcess.destroyForcibly(); + } + + public boolean isAlive() { + return nativeProcess != null && nativeProcess.isAlive(); + } + + private void writeParams() { + try { + process.runWriteParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort)); + } catch (Throwable e) { + terminate(); + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java index 55f94531..1e00a069 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java @@ -2,6 +2,7 @@ import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.LauncherConfig; +import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.client.ClientLauncherEntryPoint; import pro.gravit.launcher.client.ClientParams; import pro.gravit.launcher.runtime.LauncherEngine; @@ -69,6 +70,12 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir, ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) { + this(clientDir, assetDir, javaVersion, resourcePackDir, profile, playerProfile, view, accessToken, clientHDir, assetHDir, jvmHDir, null, null); + } + + public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir, + ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, + HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir, AuthRequestEvent.OAuthRequestEvent oAuthRequestEvent, String authId) { this.javaVersion = javaVersion; this.workDir = clientDir.toAbsolutePath(); this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir); @@ -77,6 +84,8 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString(); this.params.timestamp = System.currentTimeMillis(); + this.params.oauth = oAuthRequestEvent; + this.params.authId = authId; Path nativesPath; if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) { nativesPath = workDir.resolve("natives"); @@ -119,10 +128,8 @@ private void applyClientProfile() { if (params.ram > 0) { this.jvmArgs.add("-Xmx" + params.ram + 'M'); } - this.params.oauth = Request.getOAuth(); if (this.params.oauth == null) { - throw new UnsupportedOperationException("Legacy session not supported"); - } else { + this.params.oauth = Request.getOAuth(); this.params.authId = Request.getAuthId(); this.params.oauthExpiredTime = Request.getTokenExpiredTime(); this.params.extendedTokens = Request.getExtendedTokens(); diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java index 6f31c359..7e2f6130 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.runtime.client; import com.google.gson.GsonBuilder; +import pro.gravit.launcher.core.backend.UserSettings; import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.core.managers.GsonManager; import pro.gravit.launcher.base.modules.events.PreGsonPhase; diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java index 53f113b2..7f703e30 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java @@ -5,6 +5,7 @@ import com.google.gson.JsonParser; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfileVersions; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; import pro.gravit.launcher.core.serialize.HInput; import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.utils.helper.IOHelper; @@ -18,6 +19,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; +import java.util.List; import java.util.Objects; import java.util.regex.Pattern; @@ -208,7 +210,7 @@ public Result ping() throws IOException { } } - public static final class Result { + public static final class Result implements LauncherBackendAPI.ServerPingInfo { public final int onlinePlayers; @@ -228,5 +230,20 @@ public Result(int onlinePlayers, int maxPlayers, String raw) { public boolean isOverfilled() { return onlinePlayers >= maxPlayers; } + + @Override + public int getMaxOnline() { + return maxPlayers; + } + + @Override + public int getOnline() { + return onlinePlayers; + } + + @Override + public List getPlayerNames() { + return List.of(); + } } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java new file mode 100644 index 00000000..bd4e21bc --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java @@ -0,0 +1,65 @@ +package pro.gravit.launcher.runtime.utils; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launcher.core.hasher.HashedEntry; +import pro.gravit.utils.helper.IOHelper; + +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Path; +import java.util.*; + +public class AssetIndexHelper { + + public static AssetIndex parse(Path path) throws IOException { + try (Reader reader = IOHelper.newReader(path)) { + return Launcher.gsonManager.gson.fromJson(reader, AssetIndex.class); + } + } + + public static void modifyHashedDir(AssetIndex index, HashedDir original) { + Set hashes = new HashSet<>(); + for (AssetIndexObject obj : index.objects.values()) { + hashes.add(obj.hash); + } + HashedDir objects = (HashedDir) original.getEntry("objects"); + List toDeleteDirs = new ArrayList<>(16); + for (Map.Entry entry : objects.map().entrySet()) { + if (entry.getValue().getType() != HashedEntry.Type.DIR) { + continue; + } + HashedDir dir = (HashedDir) entry.getValue(); + List toDelete = new ArrayList<>(16); + for (String hash : dir.map().keySet()) { + if (!hashes.contains(hash)) { + toDelete.add(hash); + } + } + for (String s : toDelete) { + dir.remove(s); + } + if (dir.map().isEmpty()) { + toDeleteDirs.add(entry.getKey()); + } + } + for (String s : toDeleteDirs) { + objects.remove(s); + } + } + + public static class AssetIndex { + @LauncherNetworkAPI + public boolean virtual; + @LauncherNetworkAPI + public Map objects; + } + + public static class AssetIndexObject { + @LauncherNetworkAPI + public String hash; + @LauncherNetworkAPI + public long size; + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java new file mode 100644 index 00000000..89fb1a27 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java @@ -0,0 +1,10 @@ +package pro.gravit.launcher.runtime.utils; + +import com.sun.management.OperatingSystemMXBean; + +public class SystemMemory { + private static final OperatingSystemMXBean systemMXBean = (OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean(); + public static long getPhysicalMemorySize() { + return systemMXBean.getTotalMemorySize(); + } +} diff --git a/LauncherAPI/build.gradle b/LauncherAPI/build.gradle index d66ee2a7..76b3a3eb 100644 --- a/LauncherAPI/build.gradle +++ b/LauncherAPI/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' dependencies { api project(':LauncherCore') diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java index bfa23a50..fdd718b8 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java @@ -1,10 +1,11 @@ package pro.gravit.launcher.base; import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.model.UserPermissions; import java.util.*; -public class ClientPermissions { +public class ClientPermissions implements UserPermissions { public static final ClientPermissions DEFAULT = new ClientPermissions(); @LauncherNetworkAPI private List roles; @@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() { return perm; } + @Override public boolean hasRole(String role) { return roles != null && roles.contains(role); } @@ -46,6 +48,7 @@ public synchronized void compile() { } } + @Override public boolean hasPerm(String action) { if (available == null) { compile(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java index 01c80c8d..980411d6 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java @@ -234,7 +234,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do return task.get(); } - protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { + public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException { URI uri; if(baseUri != null) { String scheme = baseUri.getScheme(); @@ -247,6 +247,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy } else { uri = new URI(filePath); } + return uri; + } + + protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { + var uri = makeURI(baseUri, filePath); return HttpRequest.newBuilder() .GET() .uri(uri) diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java index a03007d3..a7e0584e 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.events.request; import pro.gravit.launcher.base.ClientPermissions; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.PlayerProfile; @@ -67,7 +68,15 @@ public String getType() { return "auth"; } - public static class OAuthRequestEvent { + public CurrentUserRequestEvent.UserInfo makeUserInfo() { + var userInfo = new CurrentUserRequestEvent.UserInfo(); + userInfo.accessToken = accessToken; + userInfo.permissions = permissions; + userInfo.playerProfile = playerProfile; + return userInfo; + } + + public static class OAuthRequestEvent implements AuthFeatureAPI.AuthToken { public final String accessToken; public final String refreshToken; public final long expire; @@ -77,5 +86,26 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) { this.refreshToken = refreshToken; this.expire = expire; } + + public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) { + this.accessToken = token.getAccessToken(); + this.refreshToken = token.getRefreshToken(); + this.expire = token.getExpire(); + } + + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public String getRefreshToken() { + return refreshToken; + } + + @Override + public long getExpire() { + return expire; + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java index 67c30dbb..37647de4 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java @@ -3,6 +3,11 @@ import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.PlayerProfile; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.Map; +import java.util.UUID; public class CurrentUserRequestEvent extends RequestEvent { public final UserInfo userInfo; @@ -16,7 +21,7 @@ public String getType() { return "currentUser"; } - public static class UserInfo { + public static class UserInfo implements SelfUser { public ClientPermissions permissions; public String accessToken; public PlayerProfile playerProfile; @@ -29,5 +34,35 @@ public UserInfo(ClientPermissions permissions, String accessToken, PlayerProfile this.accessToken = accessToken; this.playerProfile = playerProfile; } + + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public ClientPermissions getPermissions() { + return permissions; + } + + @Override + public String getUsername() { + return playerProfile.getUsername(); + } + + @Override + public UUID getUUID() { + return playerProfile.getUUID(); + } + + @Override + public Map getAssets() { + return playerProfile.getAssets(); + } + + @Override + public Map getProperties() { + return playerProfile.getProperties(); + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java index efe0fbca..25169ec4 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java @@ -1,9 +1,16 @@ package pro.gravit.launcher.base.events.request; +import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails; +import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails; import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.events.RequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; +import pro.gravit.launcher.core.api.method.details.AuthPasswordDetails; +import pro.gravit.launcher.core.api.method.details.AuthWebDetails; import pro.gravit.utils.TypeSerializeInterface; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -37,10 +44,11 @@ public enum ServerFeature { } } - public interface AuthAvailabilityDetails extends TypeSerializeInterface { + public interface AuthAvailabilityDetails extends AuthMethodDetails, TypeSerializeInterface { + AuthMethodDetails toAuthMethodDetails(); } - public static class AuthAvailability { + public static class AuthAvailability implements AuthMethod { public final List details; @LauncherNetworkAPI public String name; @@ -59,5 +67,34 @@ public AuthAvailability(List details, String name, Stri this.visible = visible; this.features = features; } + + @Override + public List getDetails() { + List convert = new ArrayList<>(); + for(var e : details) { + convert.add(e.toAuthMethodDetails()); + } + return convert; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public boolean isVisible() { + return visible; + } + + @Override + public Set getFeatures() { + return features; + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java index 90e0a1c9..82cbcbbd 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java @@ -6,6 +6,7 @@ import pro.gravit.launcher.base.profiles.optional.OptionalDepend; import pro.gravit.launcher.base.profiles.optional.OptionalFile; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.launch.LaunchOptions; @@ -13,8 +14,9 @@ import java.lang.reflect.Type; import java.net.InetSocketAddress; import java.util.*; +import java.util.stream.Collectors; -public final class ClientProfile implements Comparable { +public final class ClientProfile implements Comparable, ProfileFeatureAPI.ClientProfile { private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher( new String[0], new String[]{"indexes", "objects"}, new String[0]); @LauncherNetworkAPI @@ -284,10 +286,25 @@ public String toString() { return String.format("%s (%s)", title, uuid); } + @Override + public String getName() { + return title; + } + public UUID getUUID() { return uuid; } + @Override + public String getDescription() { + return info; + } + + @Override + public List getOptionalMods() { + return updateOptional.stream().collect(Collectors.toUnmodifiableList()); + } + public boolean hasFlag(CompatibilityFlags flag) { return flags.contains(flag); } @@ -361,6 +378,11 @@ public Map getProperties() { return Collections.unmodifiableMap(properties); } + @Override + public ServerInfo getServer() { + return getDefaultServerProfile(); + } + public List getCompatClasses() { return Collections.unmodifiableList(compatClasses); } @@ -498,7 +520,7 @@ public JsonElement serialize(Version src, Type typeOfSrc, JsonSerializationConte } } - public static class ServerProfile { + public static class ServerProfile implements ServerInfo { public String name; public String serverAddress; public int serverPort; @@ -525,6 +547,16 @@ public ServerProfile(String name, String serverAddress, int serverPort, boolean public InetSocketAddress toSocketAddress() { return InetSocketAddress.createUnresolved(serverAddress, serverPort); } + + @Override + public String getAddress() { + return serverAddress; + } + + @Override + public int getPort() { + return serverPort; + } } public static class ProfileDefaultSettings { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java index e0190993..dc778c39 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java @@ -1,5 +1,6 @@ package pro.gravit.launcher.base.profiles; +import pro.gravit.launcher.core.api.model.User; import pro.gravit.utils.helper.IOHelper; import java.util.HashMap; @@ -7,7 +8,7 @@ import java.util.Objects; import java.util.UUID; -public final class PlayerProfile { +public final class PlayerProfile implements User { public final UUID uuid; public final String username; @@ -41,6 +42,14 @@ public PlayerProfile(UUID uuid, String username, Map assets, Ma this.properties = properties; } + @SuppressWarnings({"unchecked", "rawtypes"}) + public PlayerProfile(User user) { + this.uuid = user.getUUID(); + this.username = user.getUsername(); + this.assets = new HashMap<>((Map) user.getAssets()); + this.properties = user.getProperties(); + } + public static PlayerProfile newOfflineProfile(String username) { return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>()); } @@ -49,4 +58,24 @@ public static UUID offlineUUID(String username) { return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username)); } + @Override + public String getUsername() { + return username; + } + + @Override + public UUID getUUID() { + return uuid; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public Map getAssets() { + return (Map) assets; + } + + @Override + public Map getProperties() { + return properties; + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java index 568f1d14..dee9b732 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java @@ -14,7 +14,7 @@ import java.util.Arrays; import java.util.Map; -public final class Texture extends StreamObject { +public final class Texture extends StreamObject implements pro.gravit.launcher.core.api.model.Texture { private static final SecurityHelper.DigestAlgorithm DIGEST_ALGO = SecurityHelper.DigestAlgorithm.SHA256; // Instance @@ -85,4 +85,19 @@ public String toString() { ", metadata=" + metadata + '}'; } + + @Override + public String getUrl() { + return url; + } + + @Override + public String getHash() { + return SecurityHelper.toHex(digest); + } + + @Override + public Map getMetadata() { + return metadata; + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java index 4d49cf23..d16a13a8 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java @@ -2,11 +2,13 @@ import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; import java.util.List; import java.util.Objects; +import java.util.Set; -public class OptionalFile { +public class OptionalFile implements ProfileFeatureAPI.OptionalMod { @LauncherNetworkAPI public List actions; @LauncherNetworkAPI @@ -57,10 +59,25 @@ public String getName() { return name; } + @Override + public String getDescription() { + return info; + } + + @Override + public String getCategory() { + return category; + } + public boolean isVisible() { return visible; } + @Override + public Set getDependencies() { + return Set.of(dependencies); + } + public boolean isMark() { return mark; } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java new file mode 100644 index 00000000..66d54a9c --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java @@ -0,0 +1,31 @@ +package pro.gravit.launcher.base.request; + +import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest; +import pro.gravit.launcher.base.request.update.LauncherRequest; +import pro.gravit.launcher.core.api.features.CoreFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethod; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class RequestCoreFeatureAPIImpl implements CoreFeatureAPI { + private final RequestService request; + + public RequestCoreFeatureAPIImpl(RequestService request) { + this.request = request; + } + + + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getAuthMethods() { + return request.request(new GetAvailabilityAuthRequest()).thenApply(response -> (List) response.list); + } + + @Override + public CompletableFuture checkUpdates() { + return request.request(new LauncherRequest()).thenApply(response -> new LauncherUpdateInfo(response.url, + "Unknown", response.needUpdate, response.needUpdate)); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestException.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestException.java index 52a83cab..884c2415 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestException.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestException.java @@ -2,7 +2,7 @@ import java.io.IOException; -public final class RequestException extends IOException { +public final class RequestException extends RuntimeException { public RequestException(String message) { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java new file mode 100644 index 00000000..39994d24 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -0,0 +1,191 @@ +package pro.gravit.launcher.base.request; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.request.auth.*; +import pro.gravit.launcher.base.request.auth.password.*; +import pro.gravit.launcher.base.request.update.ProfilesRequest; +import pro.gravit.launcher.base.request.update.UpdateRequest; +import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest; +import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.method.password.AuthChainPassword; +import pro.gravit.launcher.core.api.method.password.AuthOAuthPassword; +import pro.gravit.launcher.core.api.method.password.AuthPlainPassword; +import pro.gravit.launcher.core.api.method.password.AuthTotpPassword; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.User; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.utils.helper.SecurityHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI { + private final RequestService request; + private final String authId; + + public RequestFeatureAPIImpl(RequestService request, String authId) { + this.request = request; + this.authId = authId; + } + + @Override + public CompletableFuture getCurrentUser() { + return request.request(new CurrentUserRequest()).thenApply(response -> response.userInfo); + } + + @Override + public CompletableFuture auth(String login, AuthMethodPassword password) { + AuthRequest.ConnectTypes connectType = AuthRequest.ConnectTypes.API; + if(Request.getExtendedTokens() != null && Request.getExtendedTokens().get("launcher") != null) { + connectType = AuthRequest.ConnectTypes.CLIENT; + } + return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, connectType)) + .thenApply(response -> new AuthResponse(response.makeUserInfo(), response.oauth)); + } + + private AuthRequest.AuthPasswordInterface convertAuthPasswordAll(AuthMethodPassword password) { + AuthRequest.AuthPasswordInterface requestPassword; + if(password instanceof AuthChainPassword chain) { + if(chain.list().size() == 1) { + requestPassword = convertAuthPassword(chain.list().get(0)); + } else if(chain.list().size() == 2) { + requestPassword = new Auth2FAPassword(convertAuthPassword(chain.list().get(0)), + convertAuthPassword(chain.list().get(1))); + } else { + var multi = new AuthMultiPassword(); + for(var e : chain.list()) { + multi.list.add(convertAuthPassword(e)); + } + requestPassword = multi; + } + } else { + requestPassword = convertAuthPassword(password); + } + return requestPassword; + } + + private AuthRequest.AuthPasswordInterface convertAuthPassword(AuthMethodPassword password) { + if(password instanceof AuthPlainPassword plain) { + String encryptKey = Launcher.getConfig().passwordEncryptKey; + if(encryptKey != null) { + try { + return new AuthAESPassword(SecurityHelper.encrypt(encryptKey, plain.value())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + return new pro.gravit.launcher.base.request.auth.password.AuthPlainPassword(plain.value()); + } + } else if(password instanceof AuthTotpPassword totp) { + return new AuthTOTPPassword(totp.value()); + } else if(password instanceof AuthOAuthPassword oauth) { + return new AuthCodePassword(oauth.redirectUrl()); + } else if(password instanceof AuthRequest.AuthPasswordInterface custom) { + return custom; + } else if(password == null) { + return null; + } + else { + throw new UnsupportedOperationException(); + } + } + + @Override + public CompletableFuture getUserByUsername(String username) { + return request.request(new ProfileByUsernameRequest(username)).thenApply(response -> response.playerProfile); + } + + @Override + public CompletableFuture getUserByUUID(UUID uuid) { + return request.request(new ProfileByUUIDRequest(uuid)).thenApply(response -> response.playerProfile); + } + + @Override + public CompletableFuture joinServer(String username, String accessToken, String serverID) { + return request.request(new JoinServerRequest(username, accessToken, serverID)).thenCompose(response -> { + if(response.allow) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new RequestException("Not allowed")); + } + }); + } + + @Override + public CompletableFuture joinServer(UUID uuid, String accessToken, String serverID) { + return request.request(new JoinServerRequest(uuid, accessToken, serverID)).thenCompose(response -> { + if(response.allow) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new RequestException("Not allowed")); + } + }); + } + + @Override + public CompletableFuture checkServer(String username, String serverID, boolean extended) { + return request.request(new CheckServerRequest(username, serverID, extended, extended)) + .thenApply(response -> new CheckServerResponse(response.playerProfile, response.hardwareId, + response.sessionId, response.sessionProperties)); + } + + @Override + public CompletableFuture refreshToken(String refreshToken) { + return request.request(new RefreshTokenRequest(authId, refreshToken)).thenApply(response -> response.oauth); + } + + @Override + public CompletableFuture restore(String accessToken, boolean fetchUser) { + Map extended = new HashMap<>(); + if(Request.getExtendedTokens() != null) { // TODO: Control extended token + for(var e : Request.getExtendedTokens().entrySet()) { + extended.put(e.getKey(), e.getValue().token); + } + } + return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> { + // TODO: invalidToken process + return e.userInfo; + }); + } + + @Override + public CompletableFuture exit() { + return request.request(new ExitRequest()).thenApply(response -> null); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getProfiles() { + return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles); + } + + @Override + public CompletableFuture changeCurrentProfile(ClientProfile profile) { + return request.request(new SetProfileRequest((pro.gravit.launcher.base.profiles.ClientProfile) profile)).thenApply(response -> null); + } + + @Override + public CompletableFuture fetchUpdateInfo(String dirName) { + return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url)); + } + + public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo { + @Override + public HashedDir getHashedDir() { + return hdir; + } + + @Override + public String getUrl() { + return url; + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java index da8ff171..747627ba 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java @@ -5,25 +5,21 @@ import java.util.concurrent.ExecutionException; public interface RequestService { - CompletableFuture request(Request request) throws IOException; + CompletableFuture request(Request request); void connect() throws Exception; void registerEventHandler(EventHandler handler); void unregisterEventHandler(EventHandler handler); - default T requestSync(Request request) throws IOException { + default T requestSync(Request request) { try { return request(request).get(); } catch (InterruptedException e) { throw new RequestException("Request interrupted"); } catch (ExecutionException e) { Throwable cause = e.getCause(); - if (cause instanceof IOException) - throw (IOException) e.getCause(); - else { - throw new RequestException(cause); - } + throw new RequestException(cause); } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java index 5cbc489f..59ab371b 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java @@ -5,6 +5,7 @@ import pro.gravit.launcher.base.request.Request; import pro.gravit.launcher.base.request.auth.password.*; import pro.gravit.launcher.base.request.websockets.WebSocketRequest; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; import pro.gravit.utils.ProviderMap; public final class AuthRequest extends Request implements WebSocketRequest { @@ -64,7 +65,7 @@ public enum ConnectTypes { API } - public interface AuthPasswordInterface { + public interface AuthPasswordInterface extends AuthMethodPassword { boolean check(); default boolean isAllowSave() { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java index a5813a17..68630ac3 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java @@ -1,10 +1,16 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthLoginOnlyDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { @Override public String getType() { return "loginonly"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthLoginOnlyDetails(); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java index 825fa36f..2cbc66bb 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthPasswordDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { @Override @@ -9,4 +10,8 @@ public String getType() { } + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthPasswordDetails(); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java index 80c4048e..fdbce796 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthTotpDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public final String alg; @@ -20,4 +21,9 @@ public AuthTotpDetails(String alg) { public String getType() { return "totp"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthTotpDetails(maxKeyLength); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java index 3090d251..8b4a6593 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java @@ -1,6 +1,8 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; +import pro.gravit.launcher.core.api.method.details.AuthWebDetails; public class AuthWebViewDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public final String url; @@ -26,4 +28,9 @@ public AuthWebViewDetails(String url, String redirectUrl) { public String getType() { return "webview"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new AuthWebDetails(url, redirectUrl, canBrowser); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java index 05672942..9732d69f 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java @@ -6,6 +6,14 @@ public class Auth2FAPassword implements AuthRequest.AuthPasswordInterface { public AuthRequest.AuthPasswordInterface firstPassword; public AuthRequest.AuthPasswordInterface secondPassword; + public Auth2FAPassword() { + } + + public Auth2FAPassword(AuthRequest.AuthPasswordInterface firstPassword, AuthRequest.AuthPasswordInterface secondPassword) { + this.firstPassword = firstPassword; + this.secondPassword = secondPassword; + } + @Override public boolean check() { return firstPassword != null && firstPassword.check() && secondPassword != null && secondPassword.check(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java index 4ddccec6..00c2aec1 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java @@ -5,6 +5,13 @@ public class AuthTOTPPassword implements AuthRequest.AuthPasswordInterface { public String totp; + public AuthTOTPPassword() { + } + + public AuthTOTPPassword(String totp) { + this.totp = totp; + } + @Override public boolean check() { return true; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java index 1f998bf3..16a64652 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java @@ -97,10 +97,14 @@ public void eventHandle(T webSocketEvent) { processEventHandlers(webSocketEvent); } - public CompletableFuture request(Request request) throws IOException { + public CompletableFuture request(Request request) { CompletableFuture result = new CompletableFuture<>(); futureMap.put(request.requestUUID, result); - sendObject(request, WebSocketRequest.class); + try { + sendObject(request, WebSocketRequest.class); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } return result; } @@ -114,18 +118,14 @@ public void unregisterEventHandler(RequestService.EventHandler handler) { eventHandlers.remove(handler); } - public T requestSync(Request request) throws IOException { + public T requestSync(Request request) { try { return request(request).get(); } catch (InterruptedException e) { throw new RequestException("Request interrupted"); } catch (ExecutionException e) { Throwable cause = e.getCause(); - if (cause instanceof IOException) - throw (IOException) e.getCause(); - else { - throw new RequestException(cause); - } + throw new RequestException(cause); } } diff --git a/LauncherClient/build.gradle b/LauncherClient/build.gradle index 78ad4310..8562cfe8 100644 --- a/LauncherClient/build.gradle +++ b/LauncherClient/build.gradle @@ -7,8 +7,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/LauncherCore/build.gradle b/LauncherCore/build.gradle index 068bbf70..131a0dc9 100644 --- a/LauncherCore/build.gradle +++ b/LauncherCore/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' dependencies { compileOnly group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java new file mode 100644 index 00000000..206c27d7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java @@ -0,0 +1,34 @@ +package pro.gravit.launcher.core.api; + +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.FeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; + +import java.util.HashMap; +import java.util.Map; + +public class LauncherAPI { + private final Map, FeatureAPI> map; + + public LauncherAPI(Map, FeatureAPI> map) { + this.map = new HashMap<>(map); + } + + public AuthFeatureAPI auth() { + return get(AuthFeatureAPI.class); + } + + public UserFeatureAPI user() { + return get(UserFeatureAPI.class); + } + + public ProfileFeatureAPI profile() { + return get(ProfileFeatureAPI.class); + } + + @SuppressWarnings("unchecked") + public T get(Class clazz) { + return (T) map.get(clazz); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java new file mode 100644 index 00000000..048281e7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java @@ -0,0 +1,70 @@ +package pro.gravit.launcher.core.api; + +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.CoreFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public final class LauncherAPIHolder { + private static volatile CoreFeatureAPI coreAPI; + private static volatile LauncherAPI api; + private static volatile Function createApiFactory; + private static final Map map = new ConcurrentHashMap<>(); + + public static void setCoreAPI(CoreFeatureAPI coreAPI) { + LauncherAPIHolder.coreAPI = coreAPI; + } + + public static void setApi(LauncherAPI api) { + LauncherAPIHolder.api = api; + } + + public static void setCreateApiFactory(Function createApiFactory) { + LauncherAPIHolder.createApiFactory = createApiFactory; + } + + public static void changeAuthId(String authId) { + LauncherAPIHolder.api = map.computeIfAbsent(authId, createApiFactory); + } + + public static LauncherAPI get() { + return api; + } + + public static LauncherAPI get(String authId) { + return map.computeIfAbsent(authId, createApiFactory); + } + + public static CoreFeatureAPI core() { + return coreAPI; + } + + public static AuthFeatureAPI auth() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.auth(); + } + + public static UserFeatureAPI user() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.user(); + } + + public static ProfileFeatureAPI profile() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.profile(); + } + + public static void set(LauncherAPI api) { + LauncherAPIHolder.api = api; + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java new file mode 100644 index 00000000..04375268 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java @@ -0,0 +1,24 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.model.SelfUser; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface AuthFeatureAPI extends FeatureAPI { + CompletableFuture getCurrentUser(); + CompletableFuture auth(String login, AuthMethodPassword password); + CompletableFuture refreshToken(String refreshToken); + CompletableFuture restore(String accessToken, boolean fetchUser); + CompletableFuture exit(); + + record AuthResponse(SelfUser user, AuthToken authToken) {} + + interface AuthToken { + String getAccessToken(); + String getRefreshToken(); + long getExpire(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java new file mode 100644 index 00000000..1b1c241f --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java @@ -0,0 +1,13 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.method.AuthMethod; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface CoreFeatureAPI { + CompletableFuture> getAuthMethods(); + CompletableFuture checkUpdates(); + + record LauncherUpdateInfo(String url, String version, boolean available, boolean required) {} +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java new file mode 100644 index 00000000..2a71e92c --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.features; + +public interface FeatureAPI { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java new file mode 100644 index 00000000..ddf7d032 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -0,0 +1,43 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.hasher.HashedDir; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface ProfileFeatureAPI extends FeatureAPI { + CompletableFuture> getProfiles(); + CompletableFuture changeCurrentProfile(ClientProfile profile); + CompletableFuture fetchUpdateInfo(String dirName); + + interface UpdateInfo { + HashedDir getHashedDir(); + String getUrl(); + } + + interface ClientProfile { + String getName(); + UUID getUUID(); + String getDescription(); + List getOptionalMods(); + String getProperty(String name); + Map getProperties(); + ServerInfo getServer(); + + interface ServerInfo { + String getAddress(); + int getPort(); + } + } + + interface OptionalMod { + String getName(); + String getDescription(); + String getCategory(); + boolean isVisible(); + Set getDependencies(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java new file mode 100644 index 00000000..cba41c1a --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java @@ -0,0 +1,20 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public interface TextureUploadFeatureAPI { + CompletableFuture fetchInfo(); + CompletableFuture upload(String name, byte[] bytes, UploadSettings settings); + + interface TextureUploadInfo { + Set getAvailable(); + boolean isRequireManualSlimSkinSelect(); + } + + record UploadSettings(boolean slim) { + + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java new file mode 100644 index 00000000..7fec7265 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java @@ -0,0 +1,37 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.model.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public interface UserFeatureAPI extends FeatureAPI { + CompletableFuture getUserByUsername(String username); + CompletableFuture getUserByUUID(UUID uuid); + CompletableFuture joinServer(String username, String accessToken, String serverID); + CompletableFuture joinServer(UUID uuid, String accessToken, String serverID); + CompletableFuture checkServer(String username, String serverID, boolean extended); + default CompletableFuture> getUsersByUsernames(List usernames) { + List> list = new ArrayList<>(); + for(var username : usernames) { + list.add(getUserByUsername(username)); + } + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)).thenApply(x -> { + List r = new ArrayList<>(); + for(var e : list) { + try { + r.add(e.get()); + } catch (InterruptedException | ExecutionException ex) { + r.add(null); + } + } + return r; + }); + } + + record CheckServerResponse(User user, String hardwareId, String sessionId, Map sessionProperties) {} +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java new file mode 100644 index 00000000..e4881074 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java @@ -0,0 +1,12 @@ +package pro.gravit.launcher.core.api.method; + +import java.util.List; +import java.util.Set; + +public interface AuthMethod { + List getDetails(); + String getName(); + String getDisplayName(); + boolean isVisible(); + Set getFeatures(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java new file mode 100644 index 00000000..27cbed00 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.method; + +public interface AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java new file mode 100644 index 00000000..de8854d1 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.method; + +public interface AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java new file mode 100644 index 00000000..535d5d3f --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public class AuthLoginOnlyDetails implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java new file mode 100644 index 00000000..36e519a4 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public class AuthPasswordDetails implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java new file mode 100644 index 00000000..ee4f1899 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public record AuthTotpDetails(int length) implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java new file mode 100644 index 00000000..6dba7293 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public record AuthWebDetails(String url, String redirectUrl, boolean externalBrowserSupport) implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java new file mode 100644 index 00000000..f95f7773 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java @@ -0,0 +1,8 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +import java.util.List; + +public record AuthChainPassword(List list) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java new file mode 100644 index 00000000..40af34e0 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthOAuthPassword(String redirectUrl) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java new file mode 100644 index 00000000..d0d1fbf7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthPlainPassword(String value) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java new file mode 100644 index 00000000..e63f4f37 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthTotpPassword(String value) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java new file mode 100644 index 00000000..3345be64 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.model; + +public interface SelfUser extends User { + String getAccessToken(); + UserPermissions getPermissions(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java new file mode 100644 index 00000000..4fd79f98 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java @@ -0,0 +1,9 @@ +package pro.gravit.launcher.core.api.model; + +import java.util.Map; + +public interface Texture { + String getUrl(); + String getHash(); + Map getMetadata(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java new file mode 100644 index 00000000..5d1fbd07 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java @@ -0,0 +1,11 @@ +package pro.gravit.launcher.core.api.model; + +import java.util.Map; +import java.util.UUID; + +public interface User { + String getUsername(); + UUID getUUID(); + Map getAssets(); + Map getProperties(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java new file mode 100644 index 00000000..6f6520e8 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.model; + +public interface UserPermissions { + boolean hasRole(String role); + boolean hasPerm(String action); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java new file mode 100644 index 00000000..e367f11b --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -0,0 +1,175 @@ +package pro.gravit.launcher.core.backend; + +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.Texture; +import pro.gravit.launcher.core.api.model.UserPermissions; +import pro.gravit.launcher.core.backend.extensions.Extension; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public interface LauncherBackendAPI { + void setCallback(MainCallback callback); + CompletableFuture init(); + void selectAuthMethod(AuthMethod method); + CompletableFuture tryAuthorize(); + CompletableFuture authorize(String login, AuthMethodPassword password); + CompletableFuture> fetchProfiles(); + ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile); + void saveClientProfileSettings(ClientProfileSettings settings); + CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback); + // Tools + CompletableFuture fetchTexture(Texture texture); + CompletableFuture> getAvailableJava(); + CompletableFuture pingServer(ProfileFeatureAPI.ClientProfile profile); + // Settings + void registerUserSettings(String name, Class clazz); + UserSettings getUserSettings(String name, Function ifNotExist); + // Status + UserPermissions getPermissions(); + boolean hasPermission(String permission); + String getUsername(); + SelfUser getSelfUser(); + boolean isTestMode(); + // Extensions + T getExtension(Class clazz); + void shutdown(); + + record LauncherInitData(List methods) {} + + interface ReadyProfile { + ProfileFeatureAPI.ClientProfile getClientProfile(); + ClientProfileSettings getSettings(); + void run(RunCallback callback) throws Exception; + } + + interface ClientProfileSettings { + long getReservedMemoryBytes(MemoryClass memoryClass); + long getMaxMemoryBytes(MemoryClass memoryClass); + void setReservedMemoryBytes(MemoryClass memoryClass, long value); + Set getFlags(); + Set getAvailableFlags(); + boolean hasFlag(Flag flag); + void addFlag(Flag flag); + void removeFlag(Flag flag); + Set getEnabledOptionals(); + void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); + void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); + Java getSelectedJava(); + Java getRecommendedJava(); + void setSelectedJava(Java java); + boolean isRecommended(Java java); + boolean isCompatible(Java java); + ClientProfileSettings copy(); + + enum Flag { + @LauncherNetworkAPI + AUTO_ENTER, + @LauncherNetworkAPI + FULLSCREEN, + @LauncherNetworkAPI + LINUX_WAYLAND_SUPPORT, + @LauncherNetworkAPI + DEBUG_SKIP_FILE_MONITOR + } + + enum MemoryClass { + TOTAL + } + + interface ChangedOptionalStatusCallback { + void onChanged(ProfileFeatureAPI.OptionalMod mod, boolean enabled); + } + } + + // Callbacks + + class MainCallback { + // On any request + public void onChangeStatus(String status) { + + } + + public void onProfiles(List profiles) { + + } + + public void onAuthorize(SelfUser selfUser) { + + } + + public void onNotify(String header, String description) { + + } + + public void onShutdown() { + + } + } + + class RunCallback { + public void onStarted() { + + } + + public void onCanTerminate(Runnable terminate) { + + } + + public void onFinished(int code) { + + } + + public void onNormalOutput(byte[] buf, int offset, int size) { + + } + + public void onErrorOutput(byte[] buf, int offset, int size) { + + } + } + + class DownloadCallback { + public static final String STAGE_ASSET_VERIFY = "assetVerify"; + public static final String STAGE_HASHING = "hashing"; + public static final String STAGE_DIFF = "diff"; + public static final String STAGE_DOWNLOAD = "download"; + public static final String STAGE_DELETE_EXTRA = "deleteExtra"; + public static final String STAGE_DONE_PART = "done.part"; + public static final String STAGE_DONE = "done"; + + public void onStage(String stage) { + + } + + public void onCanCancel(Runnable cancel) { + + } + + public void onTotalDownload(long total) { + + } + + public void onCurrentDownloaded(long current) { + + } + } + + interface Java { + int getMajorVersion(); + Path getPath(); + } + + interface ServerPingInfo { + int getMaxOnline(); + int getOnline(); + List getPlayerNames(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java new file mode 100644 index 00000000..25b85a88 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java @@ -0,0 +1,13 @@ +package pro.gravit.launcher.core.backend; + +public class LauncherBackendAPIHolder { + private static volatile LauncherBackendAPI api; + + public static LauncherBackendAPI getApi() { + return api; + } + + public static void setApi(LauncherBackendAPI api) { + LauncherBackendAPIHolder.api = api; + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java similarity index 77% rename from Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java rename to LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java index 37f4a105..2dafaace 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java @@ -1,4 +1,4 @@ -package pro.gravit.launcher.runtime.client; +package pro.gravit.launcher.core.backend; import pro.gravit.utils.ProviderMap; diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java new file mode 100644 index 00000000..6890c40c --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java @@ -0,0 +1,14 @@ +package pro.gravit.launcher.core.backend.exceptions; + +public class LauncherBackendException extends RuntimeException { + public LauncherBackendException() { + } + + public LauncherBackendException(String message) { + super(message); + } + + public LauncherBackendException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java new file mode 100644 index 00000000..7597c51a --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.backend.extensions; + +public interface Extension { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java new file mode 100644 index 00000000..bab90ae7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java @@ -0,0 +1,11 @@ +package pro.gravit.launcher.core.backend.extensions; + +import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI; +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.concurrent.CompletableFuture; + +public interface TextureUploadExtension extends Extension { + CompletableFuture fetchTextureUploadInfo(); + CompletableFuture uploadTexture(String name, byte[] bytes, TextureUploadFeatureAPI.UploadSettings settings); +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java b/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java index 77285591..a6d744d1 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java @@ -1,5 +1,7 @@ package pro.gravit.utils.helper; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -186,7 +188,7 @@ public JavaVersionAndBuild() { } } - public static class JavaVersion { + public static class JavaVersion implements LauncherBackendAPI.Java { public final Path jvmDir; public final int version; public final int build; @@ -301,5 +303,15 @@ public static boolean isExistExtJavaLibrary(Path jvmDir, String name) { Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar")); return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin); } + + @Override + public int getMajorVersion() { + return version; + } + + @Override + public Path getPath() { + return jvmDir; + } } } diff --git a/LauncherStart/build.gradle b/LauncherStart/build.gradle index 225658f7..94b2c502 100644 --- a/LauncherStart/build.gradle +++ b/LauncherStart/build.gradle @@ -8,8 +8,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/ServerWrapper/build.gradle b/ServerWrapper/build.gradle index a84b4353..99c23468 100644 --- a/ServerWrapper/build.gradle +++ b/ServerWrapper/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'com.gradleup.shadow' String mainClassName = "pro.gravit.launcher.server.ServerWrapper" String mainAgentName = "pro.gravit.launcher.server.ServerAgent" @@ -14,8 +14,8 @@ } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/build.gradle b/build.gradle index bcbb6fa7..008bf9da 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id 'com.gradleup.shadow' version '9.0.0-beta15' apply false id 'maven-publish' id 'signing' id 'org.openjfx.javafxplugin' version '0.1.0' apply false @@ -30,13 +30,6 @@ maven { url "https://jitpack.io/" } - maven { - url 'https://maven.gravit-support.ru/repository/jitpack' - credentials { - username = 'gravitlauncher' - password = 'gravitlauncher' - } - } } jar { @@ -71,6 +64,10 @@ repositories { maven { url = version.endsWith('SNAPSHOT') ? getProperty('mavenSnapshotRepository') : getProperty('mavenReleaseRepository') + credentials { + username getProperty('mavenUsername') + password getProperty('mavenPassword') + } } } } diff --git a/modules b/modules index 6f699fae..acab14e4 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 6f699fae50f98cec19279092d9d5fac1ac451914 +Subproject commit acab14e403bb12649883017b14f2cf66cb1f4e68