From 6b873b50727f2f6ff9c948af960504685559ccc8 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:59:47 +0700 Subject: [PATCH] [FEATURE][EXPERIMENTAL] New API: Launcher Backend --- .../launcher/runtime/NewLauncherSettings.java | 2 +- .../runtime/backend/BackendSettings.java | 22 ++ .../runtime/backend/ClientDownloadImpl.java | 253 ++++++++++++++++++ .../runtime/backend/LauncherBackendImpl.java | 248 +++++++++++++++++ .../runtime/backend/ProfileSettingsImpl.java | 173 ++++++++++++ .../runtime/backend/ReadyProfileImpl.java | 128 +++++++++ .../runtime/client/RuntimeGsonManager.java | 1 + .../runtime/utils/AssetIndexHelper.java | 65 +++++ .../launcher/runtime/utils/SystemMemory.java | 10 + .../pro/gravit/launcher/base/Downloader.java | 7 +- .../launcher/base/profiles/PlayerProfile.java | 8 + .../base/request/RequestFeatureAPIImpl.java | 12 +- .../core/api/features/ProfileFeatureAPI.java | 5 +- .../core/backend/LauncherBackendAPI.java | 60 ++++- .../launcher/core/backend}/UserSettings.java | 2 +- .../exceptions/LauncherBackendException.java | 14 + .../pro/gravit/utils/helper/JavaHelper.java | 14 +- build.gradle | 2 +- 18 files changed, 1013 insertions(+), 13 deletions(-) create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java rename {Launcher/src/main/java/pro/gravit/launcher/runtime/client => LauncherCore/src/main/java/pro/gravit/launcher/core/backend}/UserSettings.java (77%) create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java 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..1d64ee03 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java @@ -0,0 +1,22 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.backend.UserSettings; + +import java.util.Map; +import java.util.UUID; + +public class BackendSettings extends UserSettings { + @LauncherNetworkAPI + public AuthorizationData auth; + @LauncherNetworkAPI + public Map settings; + public static class AuthorizationData { + @LauncherNetworkAPI + public String accessToken; + @LauncherNetworkAPI + public String refreshToken; + @LauncherNetworkAPI + public long 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..70601963 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java @@ -0,0 +1,253 @@ +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 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); + 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 realFiles.diff(updateInfo.getHashedDir(), 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) { + Files.createDirectory(dir.resolve(path)); + 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..cfd85d0b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java @@ -0,0 +1,248 @@ +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.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.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.concurrent.CompletableFuture; +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; + + @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(); + } + + @Override + public CompletableFuture init() { + try { + doInit(); + } catch (Throwable e) { + return CompletableFuture.failedFuture(e); + } + return LauncherAPIHolder.core().checkUpdates().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); + } + + @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) { + 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); + } 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 null; + } + + @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 CompletableFuture.completedFuture(availableJavas); + } + + @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 T getExtension(Class clazz) { + return null; + } + + @Override + public void shutdown() { + executorService.shutdownNow(); + 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..7e919d98 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -0,0 +1,173 @@ +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.*; + +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 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.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); + } + 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..7649f40b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java @@ -0,0 +1,128 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; +import pro.gravit.launcher.base.profiles.PlayerProfile; +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(); + } + process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), settings.getSelectedJava(), clientDir.path().resolve("resourcepacks"), + profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(), + clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir()); + 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/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/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/src/main/java/pro/gravit/launcher/base/Downloader.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java index 6eecdc21..f8f81f4d 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java @@ -235,7 +235,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(); @@ -248,6 +248,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/profiles/PlayerProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java index 083bc4d4..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 @@ -42,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<>()); } 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 index b8a9d53c..6aaed9ae 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -165,5 +165,15 @@ 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 {} + 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/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 index a030d5da..8149911b 100644 --- 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 @@ -11,7 +11,10 @@ public interface ProfileFeatureAPI extends FeatureAPI { CompletableFuture> getProfiles(); CompletableFuture fetchUpdateInfo(String dirName); - interface UpdateInfo {} + interface UpdateInfo { + HashedDir getHashedDir(); + String getUrl(); + } interface ClientProfile { String getName(); 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 index 36a01f49..7fa4a491 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -1,5 +1,6 @@ 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; @@ -8,8 +9,11 @@ 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); @@ -19,15 +23,22 @@ public interface LauncherBackendAPI { 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(); + // Settings + void registerUserSettings(String name, Class clazz); + UserSettings getUserSettings(String name, Function ifNotExist); // Status UserPermissions getPermissions(); boolean hasPermission(String permission); String getUsername(); + SelfUser getSelfUser(); // Extensions T getExtension(Class clazz); + void shutdown(); record LauncherInitData(List methods) {} @@ -38,20 +49,36 @@ interface ReadyProfile { } interface ClientProfileSettings { - long getReservedMemoryBytes(); - long getMaxMemoryBytes(); - void setReservedMemoryBytes(long value); - List getFlags(); + 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); - List getEnabledOptionals(); + Set getEnabledOptionals(); void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); - ClientProfileSettings clone(); + Java getSelectedJava(); + 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 { @@ -78,6 +105,10 @@ public void onAuthorize(SelfUser selfUser) { public void onNotify(String header, String description) { } + + public void onShutdown() { + + } } class RunCallback { @@ -85,6 +116,10 @@ public void onStarted() { } + public void onCanTerminate(Runnable terminate) { + + } + public void onFinished(int code) { } @@ -99,6 +134,14 @@ 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) { } @@ -115,4 +158,9 @@ public void onCurrentDownloaded(long current) { } } + + interface Java { + int getMajorVersion(); + Path getPath(); + } } 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/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/build.gradle b/build.gradle index 211d7b5d..a658cec2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.1.0' apply false } group = 'pro.gravit.launcher' -version = '5.6.2' +version = '5.7.0-SNAPSHOT' apply from: 'props.gradle'