mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-01-09 17:19:47 +03:00
[FEATURE][EXPERIMENTAL] New API: Launcher Backend
This commit is contained in:
parent
44e840734c
commit
6b873b5072
18 changed files with 1013 additions and 13 deletions
|
@ -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;
|
||||
|
|
|
@ -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<UUID, ProfileSettingsImpl> settings;
|
||||
public static class AuthorizationData {
|
||||
@LauncherNetworkAPI
|
||||
public String accessToken;
|
||||
@LauncherNetworkAPI
|
||||
public String refreshToken;
|
||||
@LauncherNetworkAPI
|
||||
public long expireIn;
|
||||
}
|
||||
}
|
|
@ -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<LauncherBackendAPI.ReadyProfile> downloadProfile(ClientProfile profile, ProfileSettingsImpl settings, LauncherBackendAPI.DownloadCallback callback) {
|
||||
AtomicReference<DownloadedDir> clientRef = new AtomicReference<>();
|
||||
AtomicReference<DownloadedDir> assetRef = new AtomicReference<>();
|
||||
AtomicReference<DownloadedDir> 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<DownloadedDir> 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<AssetData> 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<Downloader.SizedFile>();
|
||||
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<DownloadedDir> 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<DownloadedDir> 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<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback, Function<String, String> 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<HashedDir.Diff> downloadFiles(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, LauncherBackendAPI.DownloadCallback callback, HashedDir.Diff diff, Function<String, String> 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<Downloader.SizedFile> collectFilesAndCreateDirectories(Path dir, HashedDir mismatch, Function<String, String> pathRemapper) throws IOException {
|
||||
List<Downloader.SizedFile> 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<String, HashedEntry> 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<String, String> makePathRemapperFunction(LinkedList<PathRemapperData> map) {
|
||||
return (path) -> {
|
||||
for(var e : map) {
|
||||
if(path.startsWith(e.key)) {
|
||||
return e.value;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
}
|
||||
|
||||
private LinkedList<PathRemapperData> applyOptionalMods(OptionalView view, HashedDir hdir) {
|
||||
for (OptionalAction action : view.getDisabledActions()) {
|
||||
if (action instanceof OptionalActionFile optionalActionFile) {
|
||||
optionalActionFile.disableInHashedDir(hdir);
|
||||
}
|
||||
}
|
||||
LinkedList<PathRemapperData> pathRemapper = new LinkedList<>();
|
||||
Set<OptionalActionFile> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ProfileFeatureAPI.ClientProfile> profiles;
|
||||
private volatile UserPermissions permissions;
|
||||
private volatile SelfUser selfUser;
|
||||
private volatile List<Java> availableJavas;
|
||||
private volatile CompletableFuture<List<Java>> 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<LauncherInitData> 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<SelfUser> 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<SelfUser> 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<List<ProfileFeatureAPI.ClientProfile>> 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<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback) {
|
||||
return clientDownloadImpl.downloadProfile((ClientProfile) profile, (ProfileSettingsImpl) settings, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<byte[]> fetchTexture(Texture texture) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public CompletableFuture<List<Java>> 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<? extends UserSettings> clazz) {
|
||||
UserSettings.providers.register(name, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSettings getUserSettings(String name, Function<String, UserSettings> 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 extends Extension> T getExtension(Class<T> clazz) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
executorService.shutdownNow();
|
||||
try {
|
||||
settingsManager.saveConfig();
|
||||
} catch (IOException e) {
|
||||
LogHelper.error("Config not saved", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MemoryClass, Long> ram;
|
||||
@LauncherNetworkAPI
|
||||
private Set<Flag> flags;
|
||||
@LauncherNetworkAPI
|
||||
private Set<String> 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<Flag> getFlags() {
|
||||
return Collections.unmodifiableSet(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Flag> getAvailableFlags() {
|
||||
Set<Flag> 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<ProfileFeatureAPI.OptionalMod> 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) -> {});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<String> hashes = new HashSet<>();
|
||||
for (AssetIndexObject obj : index.objects.values()) {
|
||||
hashes.add(obj.hash);
|
||||
}
|
||||
HashedDir objects = (HashedDir) original.getEntry("objects");
|
||||
List<String> toDeleteDirs = new ArrayList<>(16);
|
||||
for (Map.Entry<String, HashedEntry> entry : objects.map().entrySet()) {
|
||||
if (entry.getValue().getType() != HashedEntry.Type.DIR) {
|
||||
continue;
|
||||
}
|
||||
HashedDir dir = (HashedDir) entry.getValue();
|
||||
List<String> 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<String, AssetIndexObject> objects;
|
||||
}
|
||||
|
||||
public static class AssetIndexObject {
|
||||
@LauncherNetworkAPI
|
||||
public String hash;
|
||||
@LauncherNetworkAPI
|
||||
public long size;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -42,6 +42,14 @@ public PlayerProfile(UUID uuid, String username, Map<String, Texture> 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<>());
|
||||
}
|
||||
|
|
|
@ -165,5 +165,15 @@ public CompletableFuture<UpdateInfo> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,10 @@ public interface ProfileFeatureAPI extends FeatureAPI {
|
|||
CompletableFuture<List<ClientProfile>> getProfiles();
|
||||
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
|
||||
|
||||
interface UpdateInfo {}
|
||||
interface UpdateInfo {
|
||||
HashedDir getHashedDir();
|
||||
String getUrl();
|
||||
}
|
||||
|
||||
interface ClientProfile {
|
||||
String getName();
|
||||
|
|
|
@ -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<SelfUser> authorize(String login, AuthMethodPassword password);
|
||||
CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles();
|
||||
ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile);
|
||||
void saveClientProfileSettings(ClientProfileSettings settings);
|
||||
CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback);
|
||||
// Tools
|
||||
CompletableFuture<byte[]> fetchTexture(Texture texture);
|
||||
CompletableFuture<List<Java>> getAvailableJava();
|
||||
// Settings
|
||||
void registerUserSettings(String name, Class<? extends UserSettings> clazz);
|
||||
UserSettings getUserSettings(String name, Function<String, UserSettings> ifNotExist);
|
||||
// Status
|
||||
UserPermissions getPermissions();
|
||||
boolean hasPermission(String permission);
|
||||
String getUsername();
|
||||
SelfUser getSelfUser();
|
||||
// Extensions
|
||||
<T extends Extension> T getExtension(Class<T> clazz);
|
||||
void shutdown();
|
||||
|
||||
record LauncherInitData(List<AuthMethod> methods) {}
|
||||
|
||||
|
@ -38,20 +49,36 @@ interface ReadyProfile {
|
|||
}
|
||||
|
||||
interface ClientProfileSettings {
|
||||
long getReservedMemoryBytes();
|
||||
long getMaxMemoryBytes();
|
||||
void setReservedMemoryBytes(long value);
|
||||
List<Flag> getFlags();
|
||||
long getReservedMemoryBytes(MemoryClass memoryClass);
|
||||
long getMaxMemoryBytes(MemoryClass memoryClass);
|
||||
void setReservedMemoryBytes(MemoryClass memoryClass, long value);
|
||||
Set<Flag> getFlags();
|
||||
Set<Flag> getAvailableFlags();
|
||||
boolean hasFlag(Flag flag);
|
||||
void addFlag(Flag flag);
|
||||
void removeFlag(Flag flag);
|
||||
List<ProfileFeatureAPI.OptionalMod> getEnabledOptionals();
|
||||
Set<ProfileFeatureAPI.OptionalMod> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package pro.gravit.launcher.runtime.client;
|
||||
package pro.gravit.launcher.core.backend;
|
||||
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
Loading…
Reference in a new issue