[FEATURE][EXPERIMENTAL] Merge experimental/5.7.x

This commit is contained in:
Gravita 2025-06-11 21:34:33 +07:00
commit 6110a29f5c
73 changed files with 2161 additions and 68 deletions

View file

@ -152,12 +152,8 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
} }
} }
} }
builder.setMinJavaVersion(17);
builder.setRecommendJavaVersion(17);
if(version.compareTo(ClientProfileVersions.MINECRAFT_1_20_3) >= 0) {
builder.setMinJavaVersion(21); builder.setMinJavaVersion(21);
builder.setRecommendJavaVersion(21); builder.setRecommendJavaVersion(21);
}
jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true"); jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true");
jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
builder.setJvmArgs(jvmArgs); builder.setJvmArgs(jvmArgs);

View file

@ -1,4 +1,4 @@
{ {
"features": [], "features": ["new-api"],
"info": [] "info": []
} }

View file

@ -1,4 +1,4 @@
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.gradleup.shadow'
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper" String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
@ -7,8 +7,8 @@
url "https://repo.spring.io/plugins-release/" url "https://repo.spring.io/plugins-release/"
} }
} }
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
configurations { configurations {
bundle bundle

View file

@ -2,7 +2,15 @@
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig; import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launcher.base.request.*;
import pro.gravit.launcher.client.*; import pro.gravit.launcher.client.*;
import pro.gravit.launcher.core.api.LauncherAPI;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPIHolder;
import pro.gravit.launcher.runtime.backend.LauncherBackendImpl;
import pro.gravit.launcher.runtime.client.*; import pro.gravit.launcher.runtime.client.*;
import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase; import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase;
import pro.gravit.launcher.client.events.ClientExitPhase; import pro.gravit.launcher.client.events.ClientExitPhase;
@ -19,9 +27,6 @@
import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.base.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.base.request.Request;
import pro.gravit.launcher.base.request.RequestException;
import pro.gravit.launcher.base.request.RequestService;
import pro.gravit.launcher.base.request.auth.*; import pro.gravit.launcher.base.request.auth.*;
import pro.gravit.launcher.base.request.websockets.OfflineRequestService; import pro.gravit.launcher.base.request.websockets.OfflineRequestService;
import pro.gravit.launcher.base.request.websockets.StdWebSocketService; import pro.gravit.launcher.base.request.websockets.StdWebSocketService;
@ -38,6 +43,7 @@
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -243,6 +249,17 @@ public void start(String... args) throws Throwable {
} }
Request.startAutoRefresh(); Request.startAutoRefresh();
Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler()); Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler());
// Init New API
LauncherAPIHolder.setCoreAPI(new RequestCoreFeatureAPIImpl(Request.getRequestService()));
LauncherAPIHolder.setCreateApiFactory((authId) -> {
var impl = new RequestFeatureAPIImpl(Request.getRequestService(), authId);
return new LauncherAPI(Map.of(
AuthFeatureAPI.class, impl,
UserFeatureAPI.class, impl,
ProfileFeatureAPI.class, impl));
});
LauncherBackendAPIHolder.setApi(new LauncherBackendImpl());
//
Objects.requireNonNull(args, "args"); Objects.requireNonNull(args, "args");
if (started.getAndSet(true)) if (started.getAndSet(true))
throw new IllegalStateException("Launcher has been already started"); throw new IllegalStateException("Launcher has been already started");

View file

@ -1,6 +1,6 @@
package pro.gravit.launcher.runtime; 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 pro.gravit.launcher.core.LauncherNetworkAPI;
import java.util.HashMap; import java.util.HashMap;

View file

@ -0,0 +1,43 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.backend.UserSettings;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class BackendSettings extends UserSettings {
@LauncherNetworkAPI
public AuthorizationData auth;
@LauncherNetworkAPI
public Map<UUID, ProfileSettingsImpl> settings = new HashMap<>();
public static class AuthorizationData {
@LauncherNetworkAPI
public String accessToken;
@LauncherNetworkAPI
public String refreshToken;
@LauncherNetworkAPI
public long expireIn;
public AuthFeatureAPI.AuthToken toToken() {
return new AuthFeatureAPI.AuthToken() {
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public String getRefreshToken() {
return refreshToken;
}
@Override
public long getExpire() {
return expireIn;
}
};
}
}
}

View file

@ -0,0 +1,263 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.optional.OptionalView;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalActionFile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.core.hasher.FileNameMatcher;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.hasher.HashedEntry;
import pro.gravit.launcher.core.hasher.HashedFile;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.utils.AssetIndexHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class ClientDownloadImpl {
private LauncherBackendImpl backend;
ClientDownloadImpl(LauncherBackendImpl backend) {
this.backend = backend;
}
CompletableFuture<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 LauncherAPIHolder.profile().changeCurrentProfile(profile)
.thenCompose(vv -> downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback)).thenCompose((clientDir -> {
clientRef.set(clientDir);
return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback);
})).thenCompose(assetDir -> {
assetRef.set(assetDir);
return CompletableFuture.completedFuture((DownloadedDir)null); // TODO Custom Java
}).thenCompose(javaDir -> {
javaRef.set(javaDir);
return CompletableFuture.completedFuture(null);
}).thenApply(v -> {
return new ReadyProfileImpl(backend, profile, settings, clientRef.get(), assetRef.get(), javaRef.get());
});
}
CompletableFuture<DownloadedDir> downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) {
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString+".json");
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY);
return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir);
}, backend.executorService)
.thenApply(assetData -> {
HashedDir dir = assetData.updateInfo.getHashedDir();
AssetIndexHelper.modifyHashedDir(assetData.index, dir);
return new VirtualUpdateInfo(dir, assetData.updateInfo.getUrl());
})
.thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e));
}
private CompletableFuture<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 updateInfo.getHashedDir().diff(realFiles, matcher);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, backend.executorService).thenComposeAsync((diff) -> {
return downloadFiles(targetDir, updateInfo, callback, diff, remap);
}, backend.executorService).thenApply(v -> new DownloadedDir(updateInfo.getHashedDir(), targetDir));
}
private CompletableFuture<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) {
var dirPath = dir.resolve(path);
try {
if(!Files.exists(dirPath)) {
Files.createDirectory(dirPath);
} else if (!Files.isDirectory(dirPath)) {
throw new IOException(String.format("%s is not a directory", path));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return HashedDir.WalkAction.CONTINUE;
}
String pathFixed = path.replace(File.separatorChar, '/');
files.add(new Downloader.SizedFile(pathFixed, pathRemapper.apply(pathFixed), entry.size()));
return HashedDir.WalkAction.CONTINUE;
});
return files;
}
private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean deleteDir) throws IOException {
for (Map.Entry<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;
}
}
}

View file

@ -0,0 +1,304 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.Texture;
import pro.gravit.launcher.core.api.model.UserPermissions;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.core.backend.UserSettings;
import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException;
import pro.gravit.launcher.core.backend.extensions.Extension;
import pro.gravit.launcher.runtime.NewLauncherSettings;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.client.ServerPinger;
import pro.gravit.launcher.runtime.debug.DebugMain;
import pro.gravit.launcher.runtime.managers.SettingsManager;
import pro.gravit.launcher.runtime.utils.LauncherUpdater;
import pro.gravit.utils.helper.JavaHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
public class LauncherBackendImpl implements LauncherBackendAPI {
private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this);
private volatile MainCallback callback;
ExecutorService executorService;
private volatile AuthMethod authMethod;
// Settings
private SettingsManager settingsManager;
private NewLauncherSettings allSettings;
private BackendSettings backendSettings;
// Data
private volatile List<ProfileFeatureAPI.ClientProfile> profiles;
private volatile UserPermissions permissions;
private volatile SelfUser selfUser;
private volatile List<Java> availableJavas;
private volatile CompletableFuture<List<Java>> availableJavasFuture;
private final Map<UUID, CompletableFuture<ServerPingInfo>> pingFutures = new ConcurrentHashMap<>();
@Override
public void setCallback(MainCallback callback) {
this.callback = callback;
}
private void doInit() throws Exception {
executorService = Executors.newScheduledThreadPool(2, (r) -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
});
registerUserSettings("backend", BackendSettings.class);
settingsManager = new SettingsManager();
settingsManager.generateConfigIfNotExists();
settingsManager.loadConfig();
allSettings = settingsManager.getConfig();
backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings());
permissions = new ClientPermissions();
DirBridge.dirUpdates = DirBridge.defaultUpdatesDir;
}
@Override
public CompletableFuture<LauncherInitData> init() {
try {
doInit();
} catch (Throwable e) {
return CompletableFuture.failedFuture(e);
}
CompletableFuture<CoreFeatureAPI.LauncherUpdateInfo> feature;
if(isTestMode()) {
feature = CompletableFuture.completedFuture(new CoreFeatureAPI.LauncherUpdateInfo(null, "Unknown", false, false));
} else {
feature = LauncherAPIHolder.core().checkUpdates();
}
return feature.thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> {
if(updatesInfo.required()) {
try {
LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL());
} catch (Exception e) {
throw new RuntimeException(e);
}
callback.onShutdown();
LauncherUpdater.restart();
}
return new LauncherInitData(authMethods);
}, executorService);
}
public AuthFeatureAPI.AuthToken getAuthToken() {
return backendSettings.auth.toToken();
}
public AuthMethod getAuthMethod() {
return authMethod;
}
@Override
public void selectAuthMethod(AuthMethod method) {
this.authMethod = method;
LauncherAPIHolder.changeAuthId(method.getName());
}
@Override
public CompletableFuture<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) {
this.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);
settings.backend = this;
settings.updateEnabledMods();
} else {
settings = settings.copy();
//settings.initAfterGson((ClientProfile) profile, this);
}
return settings;
}
@Override
public void saveClientProfileSettings(ClientProfileSettings settings) {
var impl = (ProfileSettingsImpl) settings;
impl.updateEnabledMods();
backendSettings.settings.put(impl.profile.getUUID(), impl);
}
@Override
public CompletableFuture<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 CompletableFuture.failedFuture(new UnsupportedOperationException());
}
@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 availableJavasFuture;
}
return CompletableFuture.completedFuture(availableJavas);
}
@Override
public CompletableFuture<ServerPingInfo> pingServer(ProfileFeatureAPI.ClientProfile profile) {
return pingFutures.computeIfAbsent(profile.getUUID(), (k) -> {
CompletableFuture<ServerPingInfo> future = new CompletableFuture<>();
executorService.submit(() -> {
try {
ServerPinger pinger = new ServerPinger((ClientProfile) profile);
future.complete(pinger.ping());
} catch (Throwable e) {
future.completeExceptionally(e);
}
});
return future;
});
}
@Override
public void registerUserSettings(String name, Class<? 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 boolean isTestMode() {
try {
return DebugMain.IS_DEBUG.get();
} catch (Throwable ex) {
return false;
}
}
@Override
public <T extends Extension> T getExtension(Class<T> clazz) {
return null;
}
@Override
public void shutdown() {
if(executorService != null) {
executorService.shutdownNow();
}
if(settingsManager != null) {
try {
settingsManager.saveConfig();
} catch (IOException e) {
LogHelper.error("Config not saved", e);
}
}
}
}

View file

@ -0,0 +1,201 @@
package pro.gravit.launcher.runtime.backend;
import oshi.SystemInfo;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.optional.OptionalFile;
import pro.gravit.launcher.base.profiles.optional.OptionalView;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.runtime.utils.SystemMemory;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.JavaHelper;
import java.util.*;
import java.util.concurrent.ExecutionException;
public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings {
transient ClientProfile profile;
transient LauncherBackendImpl backend;
@LauncherNetworkAPI
private Map<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 JavaHelper.JavaVersion getRecommendedJava() {
JavaHelper.JavaVersion result = null;
try {
for(var java : backend.getAvailableJava().get()) {
if(isRecommended(java)) {
return (JavaHelper.JavaVersion) java;
}
if(isCompatible(java)) {
if(result == null) {
result = (JavaHelper.JavaVersion) java;
continue;
}
if(result.getMajorVersion() < java.getMajorVersion()) {
result = (JavaHelper.JavaVersion) java;
}
}
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
return result;
}
@Override
public void setSelectedJava(LauncherBackendAPI.Java java) {
selectedJava = (JavaHelper.JavaVersion) java;
}
@Override
public boolean isRecommended(LauncherBackendAPI.Java java) {
return java.getMajorVersion() == profile.getRecommendJavaVersion();
}
@Override
public boolean isCompatible(LauncherBackendAPI.Java java) {
return java.getMajorVersion() >= profile.getMinJavaVersion() && java.getMajorVersion() <= profile.getMaxJavaVersion();
}
@Override
public ProfileSettingsImpl copy() {
ProfileSettingsImpl cloned = new ProfileSettingsImpl();
cloned.backend = backend;
cloned.profile = profile;
cloned.ram = new HashMap<>(ram);
cloned.flags = new HashSet<>(flags);
cloned.enabled = new HashSet<>(enabled);
if(view != null) {
cloned.view = new OptionalView(profile, view);
}
cloned.selectedJava = selectedJava;
cloned.saveJavaPath = saveJavaPath;
return cloned;
}
public void updateEnabledMods() {
enabled = new HashSet<>();
for(var e : view.enabled) {
enabled.add(e.name);
}
if(selectedJava != null) {
saveJavaPath = selectedJava.getPath().toAbsolutePath().toString();
}
}
public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {
this.backend = backend;
this.profile = profile;
this.view = new OptionalView(profile);
for(var e : enabled) {
var opt = profile.getOptionalFile(e);
if(opt == null) {
continue;
}
enableOptional(opt, (var1, var2) -> {});
}
}
}

View file

@ -0,0 +1,135 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.runtime.client.ClientLauncherProcess;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
public class ReadyProfileImpl implements LauncherBackendAPI.ReadyProfile {
private LauncherBackendImpl backend;
private ClientProfile profile;
private ProfileSettingsImpl settings;
private ClientDownloadImpl.DownloadedDir clientDir;
private ClientDownloadImpl.DownloadedDir assetDir;
private ClientDownloadImpl.DownloadedDir javaDir;
private volatile Thread writeParamsThread;
private volatile Thread runThread;
private volatile ClientLauncherProcess process;
private volatile Process nativeProcess;
private volatile LauncherBackendAPI.RunCallback callback;
public ReadyProfileImpl(LauncherBackendImpl backend, ClientProfile profile, ProfileSettingsImpl settings, ClientDownloadImpl.DownloadedDir clientDir, ClientDownloadImpl.DownloadedDir assetDir, ClientDownloadImpl.DownloadedDir javaDir) {
this.backend = backend;
this.profile = profile;
this.settings = settings;
this.clientDir = clientDir;
this.assetDir = assetDir;
this.javaDir = javaDir;
}
@Override
public ProfileFeatureAPI.ClientProfile getClientProfile() {
return profile;
}
@Override
public LauncherBackendAPI.ClientProfileSettings getSettings() {
return settings;
}
@Override
public void run(LauncherBackendAPI.RunCallback callback) {
if(isAlive()) {
terminate();
}
this.callback = callback;
if(backend.hasPermission("launcher.debug.skipfilemonitor") && settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.DEBUG_SKIP_FILE_MONITOR)) {
var builder = new ClientProfileBuilder(profile);
builder.setUpdate(new ArrayList<>());
builder.setUpdateVerify(new ArrayList<>());
builder.setUpdateExclusions(new ArrayList<>());
profile = builder.createClientProfile();
}
var java = settings.getSelectedJava();
if(java == null) {
java = settings.getRecommendedJava();
}
process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), java, clientDir.path().resolve("resourcepacks"),
profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(),
clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir(),
new AuthRequestEvent.OAuthRequestEvent(backend.getAuthToken()), backend.getAuthMethod().getName());
process.params.ram = (int) (settings.getReservedMemoryBytes(LauncherBackendAPI.ClientProfileSettings.MemoryClass.TOTAL) >> 20);
if (process.params.ram > 0) {
process.jvmArgs.add("-Xms" + process.params.ram + 'M');
process.jvmArgs.add("-Xmx" + process.params.ram + 'M');
}
process.params.fullScreen = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.FULLSCREEN);
process.params.autoEnter = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.AUTO_ENTER);
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
process.params.lwjglGlfwWayland = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.LINUX_WAYLAND_SUPPORT);
}
writeParamsThread = new Thread(this::writeParams);
writeParamsThread.setDaemon(true);
writeParamsThread.start();
runThread = new Thread(this::readThread);
runThread.setDaemon(true);
runThread.start();
}
private void readThread() {
try {
process.start(true);
nativeProcess = process.getProcess();
callback.onCanTerminate(this::terminate);
InputStream stream = nativeProcess.getInputStream();
byte[] buf = IOHelper.newBuffer();
try {
for (int length = stream.read(buf); length >= 0; length = stream.read(buf)) {
callback.onNormalOutput(buf, 0, length);
}
} catch (EOFException ignored) {
}
if (nativeProcess.isAlive()) {
int code = nativeProcess.waitFor();
callback.onFinished(code);
}
} catch (Exception e) {
if(e instanceof InterruptedException) {
return;
}
terminate();
}
}
public void terminate() {
if(nativeProcess == null) {
return;
}
nativeProcess.destroyForcibly();
}
public boolean isAlive() {
return nativeProcess != null && nativeProcess.isAlive();
}
private void writeParams() {
try {
process.runWriteParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
} catch (Throwable e) {
terminate();
}
}
}

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig; import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.client.ClientLauncherEntryPoint; import pro.gravit.launcher.client.ClientLauncherEntryPoint;
import pro.gravit.launcher.client.ClientParams; import pro.gravit.launcher.client.ClientParams;
import pro.gravit.launcher.runtime.LauncherEngine; import pro.gravit.launcher.runtime.LauncherEngine;
@ -69,6 +70,12 @@ public ClientLauncherProcess(Path clientDir, Path assetDir,
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir, public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) { HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) {
this(clientDir, assetDir, javaVersion, resourcePackDir, profile, playerProfile, view, accessToken, clientHDir, assetHDir, jvmHDir, null, null);
}
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir, AuthRequestEvent.OAuthRequestEvent oAuthRequestEvent, String authId) {
this.javaVersion = javaVersion; this.javaVersion = javaVersion;
this.workDir = clientDir.toAbsolutePath(); this.workDir = clientDir.toAbsolutePath();
this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir); this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir);
@ -77,6 +84,8 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString(); this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
this.params.assetDir = assetDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString();
this.params.timestamp = System.currentTimeMillis(); this.params.timestamp = System.currentTimeMillis();
this.params.oauth = oAuthRequestEvent;
this.params.authId = authId;
Path nativesPath; Path nativesPath;
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) { if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
nativesPath = workDir.resolve("natives"); nativesPath = workDir.resolve("natives");
@ -119,10 +128,8 @@ private void applyClientProfile() {
if (params.ram > 0) { if (params.ram > 0) {
this.jvmArgs.add("-Xmx" + params.ram + 'M'); this.jvmArgs.add("-Xmx" + params.ram + 'M');
} }
this.params.oauth = Request.getOAuth();
if (this.params.oauth == null) { if (this.params.oauth == null) {
throw new UnsupportedOperationException("Legacy session not supported"); this.params.oauth = Request.getOAuth();
} else {
this.params.authId = Request.getAuthId(); this.params.authId = Request.getAuthId();
this.params.oauthExpiredTime = Request.getTokenExpiredTime(); this.params.oauthExpiredTime = Request.getTokenExpiredTime();
this.params.extendedTokens = Request.getExtendedTokens(); this.params.extendedTokens = Request.getExtendedTokens();

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.runtime.client; package pro.gravit.launcher.runtime.client;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import pro.gravit.launcher.core.backend.UserSettings;
import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.start.RuntimeModuleManager;
import pro.gravit.launcher.core.managers.GsonManager; import pro.gravit.launcher.core.managers.GsonManager;
import pro.gravit.launcher.base.modules.events.PreGsonPhase; import pro.gravit.launcher.base.modules.events.PreGsonPhase;

View file

@ -5,6 +5,7 @@
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileVersions; import pro.gravit.launcher.base.profiles.ClientProfileVersions;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.core.serialize.HInput; import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
@ -18,6 +19,7 @@
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -208,7 +210,7 @@ public Result ping() throws IOException {
} }
} }
public static final class Result { public static final class Result implements LauncherBackendAPI.ServerPingInfo {
public final int onlinePlayers; public final int onlinePlayers;
@ -228,5 +230,20 @@ public Result(int onlinePlayers, int maxPlayers, String raw) {
public boolean isOverfilled() { public boolean isOverfilled() {
return onlinePlayers >= maxPlayers; return onlinePlayers >= maxPlayers;
} }
@Override
public int getMaxOnline() {
return maxPlayers;
}
@Override
public int getOnline() {
return onlinePlayers;
}
@Override
public List<String> getPlayerNames() {
return List.of();
}
} }
} }

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -1,5 +1,5 @@
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
dependencies { dependencies {
api project(':LauncherCore') api project(':LauncherCore')

View file

@ -1,10 +1,11 @@
package pro.gravit.launcher.base; package pro.gravit.launcher.base;
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.model.UserPermissions;
import java.util.*; import java.util.*;
public class ClientPermissions { public class ClientPermissions implements UserPermissions {
public static final ClientPermissions DEFAULT = new ClientPermissions(); public static final ClientPermissions DEFAULT = new ClientPermissions();
@LauncherNetworkAPI @LauncherNetworkAPI
private List<String> roles; private List<String> roles;
@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() {
return perm; return perm;
} }
@Override
public boolean hasRole(String role) { public boolean hasRole(String role) {
return roles != null && roles.contains(role); return roles != null && roles.contains(role);
} }
@ -46,6 +48,7 @@ public synchronized void compile() {
} }
} }
@Override
public boolean hasPerm(String action) { public boolean hasPerm(String action) {
if (available == null) { if (available == null) {
compile(); compile();

View file

@ -234,7 +234,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do
return task.get(); return task.get();
} }
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException {
URI uri; URI uri;
if(baseUri != null) { if(baseUri != null) {
String scheme = baseUri.getScheme(); String scheme = baseUri.getScheme();
@ -247,6 +247,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy
} else { } else {
uri = new URI(filePath); uri = new URI(filePath);
} }
return uri;
}
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
var uri = makeURI(baseUri, filePath);
return HttpRequest.newBuilder() return HttpRequest.newBuilder()
.GET() .GET()
.uri(uri) .uri(uri)

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.events.request; package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.profiles.PlayerProfile;
@ -67,7 +68,15 @@ public String getType() {
return "auth"; return "auth";
} }
public static class OAuthRequestEvent { public CurrentUserRequestEvent.UserInfo makeUserInfo() {
var userInfo = new CurrentUserRequestEvent.UserInfo();
userInfo.accessToken = accessToken;
userInfo.permissions = permissions;
userInfo.playerProfile = playerProfile;
return userInfo;
}
public static class OAuthRequestEvent implements AuthFeatureAPI.AuthToken {
public final String accessToken; public final String accessToken;
public final String refreshToken; public final String refreshToken;
public final long expire; public final long expire;
@ -77,5 +86,26 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
this.refreshToken = refreshToken; this.refreshToken = refreshToken;
this.expire = expire; this.expire = expire;
} }
public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) {
this.accessToken = token.getAccessToken();
this.refreshToken = token.getRefreshToken();
this.expire = token.getExpire();
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public String getRefreshToken() {
return refreshToken;
}
@Override
public long getExpire() {
return expire;
}
} }
} }

View file

@ -3,6 +3,11 @@
import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.Texture;
import java.util.Map;
import java.util.UUID;
public class CurrentUserRequestEvent extends RequestEvent { public class CurrentUserRequestEvent extends RequestEvent {
public final UserInfo userInfo; public final UserInfo userInfo;
@ -16,7 +21,7 @@ public String getType() {
return "currentUser"; return "currentUser";
} }
public static class UserInfo { public static class UserInfo implements SelfUser {
public ClientPermissions permissions; public ClientPermissions permissions;
public String accessToken; public String accessToken;
public PlayerProfile playerProfile; public PlayerProfile playerProfile;
@ -29,5 +34,35 @@ public UserInfo(ClientPermissions permissions, String accessToken, PlayerProfile
this.accessToken = accessToken; this.accessToken = accessToken;
this.playerProfile = playerProfile; this.playerProfile = playerProfile;
} }
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String getUsername() {
return playerProfile.getUsername();
}
@Override
public UUID getUUID() {
return playerProfile.getUUID();
}
@Override
public Map<String, Texture> getAssets() {
return playerProfile.getAssets();
}
@Override
public Map<String, String> getProperties() {
return playerProfile.getProperties();
}
} }
} }

View file

@ -1,9 +1,16 @@
package pro.gravit.launcher.base.events.request; package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
import pro.gravit.launcher.core.api.method.details.AuthPasswordDetails;
import pro.gravit.launcher.core.api.method.details.AuthWebDetails;
import pro.gravit.utils.TypeSerializeInterface; import pro.gravit.utils.TypeSerializeInterface;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -37,10 +44,11 @@ public enum ServerFeature {
} }
} }
public interface AuthAvailabilityDetails extends TypeSerializeInterface { public interface AuthAvailabilityDetails extends AuthMethodDetails, TypeSerializeInterface {
AuthMethodDetails toAuthMethodDetails();
} }
public static class AuthAvailability { public static class AuthAvailability implements AuthMethod {
public final List<AuthAvailabilityDetails> details; public final List<AuthAvailabilityDetails> details;
@LauncherNetworkAPI @LauncherNetworkAPI
public String name; public String name;
@ -59,5 +67,34 @@ public AuthAvailability(List<AuthAvailabilityDetails> details, String name, Stri
this.visible = visible; this.visible = visible;
this.features = features; this.features = features;
} }
@Override
public List<AuthMethodDetails> getDetails() {
List<AuthMethodDetails> convert = new ArrayList<>();
for(var e : details) {
convert.add(e.toAuthMethodDetails());
}
return convert;
}
@Override
public String getName() {
return name;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public boolean isVisible() {
return visible;
}
@Override
public Set<String> getFeatures() {
return features;
}
} }
} }

View file

@ -6,6 +6,7 @@
import pro.gravit.launcher.base.profiles.optional.OptionalDepend; import pro.gravit.launcher.base.profiles.optional.OptionalDepend;
import pro.gravit.launcher.base.profiles.optional.OptionalFile; import pro.gravit.launcher.base.profiles.optional.OptionalFile;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.helper.VerifyHelper;
import pro.gravit.utils.launch.LaunchOptions; import pro.gravit.utils.launch.LaunchOptions;
@ -13,8 +14,9 @@
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
public final class ClientProfile implements Comparable<ClientProfile> { public final class ClientProfile implements Comparable<ClientProfile>, ProfileFeatureAPI.ClientProfile {
private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher( private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher(
new String[0], new String[]{"indexes", "objects"}, new String[0]); new String[0], new String[]{"indexes", "objects"}, new String[0]);
@LauncherNetworkAPI @LauncherNetworkAPI
@ -284,10 +286,25 @@ public String toString() {
return String.format("%s (%s)", title, uuid); return String.format("%s (%s)", title, uuid);
} }
@Override
public String getName() {
return title;
}
public UUID getUUID() { public UUID getUUID() {
return uuid; return uuid;
} }
@Override
public String getDescription() {
return info;
}
@Override
public List<ProfileFeatureAPI.OptionalMod> getOptionalMods() {
return updateOptional.stream().collect(Collectors.toUnmodifiableList());
}
public boolean hasFlag(CompatibilityFlags flag) { public boolean hasFlag(CompatibilityFlags flag) {
return flags.contains(flag); return flags.contains(flag);
} }
@ -361,6 +378,11 @@ public Map<String, String> getProperties() {
return Collections.unmodifiableMap(properties); return Collections.unmodifiableMap(properties);
} }
@Override
public ServerInfo getServer() {
return getDefaultServerProfile();
}
public List<String> getCompatClasses() { public List<String> getCompatClasses() {
return Collections.unmodifiableList(compatClasses); return Collections.unmodifiableList(compatClasses);
} }
@ -498,7 +520,7 @@ public JsonElement serialize(Version src, Type typeOfSrc, JsonSerializationConte
} }
} }
public static class ServerProfile { public static class ServerProfile implements ServerInfo {
public String name; public String name;
public String serverAddress; public String serverAddress;
public int serverPort; public int serverPort;
@ -525,6 +547,16 @@ public ServerProfile(String name, String serverAddress, int serverPort, boolean
public InetSocketAddress toSocketAddress() { public InetSocketAddress toSocketAddress() {
return InetSocketAddress.createUnresolved(serverAddress, serverPort); return InetSocketAddress.createUnresolved(serverAddress, serverPort);
} }
@Override
public String getAddress() {
return serverAddress;
}
@Override
public int getPort() {
return serverPort;
}
} }
public static class ProfileDefaultSettings { public static class ProfileDefaultSettings {

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.base.profiles; package pro.gravit.launcher.base.profiles;
import pro.gravit.launcher.core.api.model.User;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.util.HashMap; import java.util.HashMap;
@ -7,7 +8,7 @@
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
public final class PlayerProfile { public final class PlayerProfile implements User {
public final UUID uuid; public final UUID uuid;
public final String username; public final String username;
@ -41,6 +42,14 @@ public PlayerProfile(UUID uuid, String username, Map<String, Texture> assets, Ma
this.properties = properties; 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) { public static PlayerProfile newOfflineProfile(String username) {
return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>()); return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>());
} }
@ -49,4 +58,24 @@ public static UUID offlineUUID(String username) {
return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username)); return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username));
} }
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Map<String, pro.gravit.launcher.core.api.model.Texture> getAssets() {
return (Map) assets;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
} }

View file

@ -14,7 +14,7 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
public final class Texture extends StreamObject { public final class Texture extends StreamObject implements pro.gravit.launcher.core.api.model.Texture {
private static final SecurityHelper.DigestAlgorithm DIGEST_ALGO = SecurityHelper.DigestAlgorithm.SHA256; private static final SecurityHelper.DigestAlgorithm DIGEST_ALGO = SecurityHelper.DigestAlgorithm.SHA256;
// Instance // Instance
@ -85,4 +85,19 @@ public String toString() {
", metadata=" + metadata + ", metadata=" + metadata +
'}'; '}';
} }
@Override
public String getUrl() {
return url;
}
@Override
public String getHash() {
return SecurityHelper.toHex(digest);
}
@Override
public Map<String, String> getMetadata() {
return metadata;
}
} }

View file

@ -2,11 +2,13 @@
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
public class OptionalFile { public class OptionalFile implements ProfileFeatureAPI.OptionalMod {
@LauncherNetworkAPI @LauncherNetworkAPI
public List<OptionalAction> actions; public List<OptionalAction> actions;
@LauncherNetworkAPI @LauncherNetworkAPI
@ -57,10 +59,25 @@ public String getName() {
return name; return name;
} }
@Override
public String getDescription() {
return info;
}
@Override
public String getCategory() {
return category;
}
public boolean isVisible() { public boolean isVisible() {
return visible; return visible;
} }
@Override
public Set<ProfileFeatureAPI.OptionalMod> getDependencies() {
return Set.of(dependencies);
}
public boolean isMark() { public boolean isMark() {
return mark; return mark;
} }

View file

@ -0,0 +1,31 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.base.request.update.LauncherRequest;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethod;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class RequestCoreFeatureAPIImpl implements CoreFeatureAPI {
private final RequestService request;
public RequestCoreFeatureAPIImpl(RequestService request) {
this.request = request;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<List<AuthMethod>> getAuthMethods() {
return request.request(new GetAvailabilityAuthRequest()).thenApply(response -> (List) response.list);
}
@Override
public CompletableFuture<LauncherUpdateInfo> checkUpdates() {
return request.request(new LauncherRequest()).thenApply(response -> new LauncherUpdateInfo(response.url,
"Unknown", response.needUpdate, response.needUpdate));
}
}

View file

@ -2,7 +2,7 @@
import java.io.IOException; import java.io.IOException;
public final class RequestException extends IOException { public final class RequestException extends RuntimeException {
public RequestException(String message) { public RequestException(String message) {

View file

@ -0,0 +1,191 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.request.auth.*;
import pro.gravit.launcher.base.request.auth.password.*;
import pro.gravit.launcher.base.request.update.ProfilesRequest;
import pro.gravit.launcher.base.request.update.UpdateRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.method.password.AuthChainPassword;
import pro.gravit.launcher.core.api.method.password.AuthOAuthPassword;
import pro.gravit.launcher.core.api.method.password.AuthPlainPassword;
import pro.gravit.launcher.core.api.method.password.AuthTotpPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.User;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.utils.helper.SecurityHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI {
private final RequestService request;
private final String authId;
public RequestFeatureAPIImpl(RequestService request, String authId) {
this.request = request;
this.authId = authId;
}
@Override
public CompletableFuture<SelfUser> getCurrentUser() {
return request.request(new CurrentUserRequest()).thenApply(response -> response.userInfo);
}
@Override
public CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword password) {
AuthRequest.ConnectTypes connectType = AuthRequest.ConnectTypes.API;
if(Request.getExtendedTokens() != null && Request.getExtendedTokens().get("launcher") != null) {
connectType = AuthRequest.ConnectTypes.CLIENT;
}
return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, connectType))
.thenApply(response -> new AuthResponse(response.makeUserInfo(), response.oauth));
}
private AuthRequest.AuthPasswordInterface convertAuthPasswordAll(AuthMethodPassword password) {
AuthRequest.AuthPasswordInterface requestPassword;
if(password instanceof AuthChainPassword chain) {
if(chain.list().size() == 1) {
requestPassword = convertAuthPassword(chain.list().get(0));
} else if(chain.list().size() == 2) {
requestPassword = new Auth2FAPassword(convertAuthPassword(chain.list().get(0)),
convertAuthPassword(chain.list().get(1)));
} else {
var multi = new AuthMultiPassword();
for(var e : chain.list()) {
multi.list.add(convertAuthPassword(e));
}
requestPassword = multi;
}
} else {
requestPassword = convertAuthPassword(password);
}
return requestPassword;
}
private AuthRequest.AuthPasswordInterface convertAuthPassword(AuthMethodPassword password) {
if(password instanceof AuthPlainPassword plain) {
String encryptKey = Launcher.getConfig().passwordEncryptKey;
if(encryptKey != null) {
try {
return new AuthAESPassword(SecurityHelper.encrypt(encryptKey, plain.value()));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return new pro.gravit.launcher.base.request.auth.password.AuthPlainPassword(plain.value());
}
} else if(password instanceof AuthTotpPassword totp) {
return new AuthTOTPPassword(totp.value());
} else if(password instanceof AuthOAuthPassword oauth) {
return new AuthCodePassword(oauth.redirectUrl());
} else if(password instanceof AuthRequest.AuthPasswordInterface custom) {
return custom;
} else if(password == null) {
return null;
}
else {
throw new UnsupportedOperationException();
}
}
@Override
public CompletableFuture<User> getUserByUsername(String username) {
return request.request(new ProfileByUsernameRequest(username)).thenApply(response -> response.playerProfile);
}
@Override
public CompletableFuture<User> getUserByUUID(UUID uuid) {
return request.request(new ProfileByUUIDRequest(uuid)).thenApply(response -> response.playerProfile);
}
@Override
public CompletableFuture<Void> joinServer(String username, String accessToken, String serverID) {
return request.request(new JoinServerRequest(username, accessToken, serverID)).thenCompose(response -> {
if(response.allow) {
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.failedFuture(new RequestException("Not allowed"));
}
});
}
@Override
public CompletableFuture<Void> joinServer(UUID uuid, String accessToken, String serverID) {
return request.request(new JoinServerRequest(uuid, accessToken, serverID)).thenCompose(response -> {
if(response.allow) {
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.failedFuture(new RequestException("Not allowed"));
}
});
}
@Override
public CompletableFuture<CheckServerResponse> checkServer(String username, String serverID, boolean extended) {
return request.request(new CheckServerRequest(username, serverID, extended, extended))
.thenApply(response -> new CheckServerResponse(response.playerProfile, response.hardwareId,
response.sessionId, response.sessionProperties));
}
@Override
public CompletableFuture<AuthToken> refreshToken(String refreshToken) {
return request.request(new RefreshTokenRequest(authId, refreshToken)).thenApply(response -> response.oauth);
}
@Override
public CompletableFuture<SelfUser> restore(String accessToken, boolean fetchUser) {
Map<String, String> extended = new HashMap<>();
if(Request.getExtendedTokens() != null) { // TODO: Control extended token
for(var e : Request.getExtendedTokens().entrySet()) {
extended.put(e.getKey(), e.getValue().token);
}
}
return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> {
// TODO: invalidToken process
return e.userInfo;
});
}
@Override
public CompletableFuture<Void> exit() {
return request.request(new ExitRequest()).thenApply(response -> null);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> getProfiles() {
return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles);
}
@Override
public CompletableFuture<Void> changeCurrentProfile(ClientProfile profile) {
return request.request(new SetProfileRequest((pro.gravit.launcher.base.profiles.ClientProfile) profile)).thenApply(response -> null);
}
@Override
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 {
@Override
public HashedDir getHashedDir() {
return hdir;
}
@Override
public String getUrl() {
return url;
}
}
}

View file

@ -5,27 +5,23 @@
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public interface RequestService { public interface RequestService {
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException; <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request);
void connect() throws Exception; void connect() throws Exception;
void registerEventHandler(EventHandler handler); void registerEventHandler(EventHandler handler);
void unregisterEventHandler(EventHandler handler); void unregisterEventHandler(EventHandler handler);
default <T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException { default <T extends WebSocketEvent> T requestSync(Request<T> request) {
try { try {
return request(request).get(); return request(request).get();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RequestException("Request interrupted"); throw new RequestException("Request interrupted");
} catch (ExecutionException e) { } catch (ExecutionException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof IOException)
throw (IOException) e.getCause();
else {
throw new RequestException(cause); throw new RequestException(cause);
} }
} }
}
boolean isClosed(); boolean isClosed();

View file

@ -5,6 +5,7 @@
import pro.gravit.launcher.base.request.Request; import pro.gravit.launcher.base.request.Request;
import pro.gravit.launcher.base.request.auth.password.*; import pro.gravit.launcher.base.request.auth.password.*;
import pro.gravit.launcher.base.request.websockets.WebSocketRequest; import pro.gravit.launcher.base.request.websockets.WebSocketRequest;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
public final class AuthRequest extends Request<AuthRequestEvent> implements WebSocketRequest { public final class AuthRequest extends Request<AuthRequestEvent> implements WebSocketRequest {
@ -64,7 +65,7 @@ public enum ConnectTypes {
API API
} }
public interface AuthPasswordInterface { public interface AuthPasswordInterface extends AuthMethodPassword {
boolean check(); boolean check();
default boolean isAllowSave() { default boolean isAllowSave() {

View file

@ -1,10 +1,16 @@
package pro.gravit.launcher.base.request.auth.details; package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthLoginOnlyDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public class AuthLoginOnlyDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
@Override @Override
public String getType() { public String getType() {
return "loginonly"; return "loginonly";
} }
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthLoginOnlyDetails();
}
} }

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.request.auth.details; package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthPasswordDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public class AuthPasswordDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
@Override @Override
@ -9,4 +10,8 @@ public String getType() {
} }
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthPasswordDetails();
}
} }

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.request.auth.details; package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthTotpDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public class AuthTotpDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
public final String alg; public final String alg;
@ -20,4 +21,9 @@ public AuthTotpDetails(String alg) {
public String getType() { public String getType() {
return "totp"; return "totp";
} }
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthTotpDetails(maxKeyLength);
}
} }

View file

@ -1,6 +1,8 @@
package pro.gravit.launcher.base.request.auth.details; package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
import pro.gravit.launcher.core.api.method.details.AuthWebDetails;
public class AuthWebViewDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public class AuthWebViewDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
public final String url; public final String url;
@ -26,4 +28,9 @@ public AuthWebViewDetails(String url, String redirectUrl) {
public String getType() { public String getType() {
return "webview"; return "webview";
} }
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new AuthWebDetails(url, redirectUrl, canBrowser);
}
} }

View file

@ -6,6 +6,14 @@ public class Auth2FAPassword implements AuthRequest.AuthPasswordInterface {
public AuthRequest.AuthPasswordInterface firstPassword; public AuthRequest.AuthPasswordInterface firstPassword;
public AuthRequest.AuthPasswordInterface secondPassword; public AuthRequest.AuthPasswordInterface secondPassword;
public Auth2FAPassword() {
}
public Auth2FAPassword(AuthRequest.AuthPasswordInterface firstPassword, AuthRequest.AuthPasswordInterface secondPassword) {
this.firstPassword = firstPassword;
this.secondPassword = secondPassword;
}
@Override @Override
public boolean check() { public boolean check() {
return firstPassword != null && firstPassword.check() && secondPassword != null && secondPassword.check(); return firstPassword != null && firstPassword.check() && secondPassword != null && secondPassword.check();

View file

@ -5,6 +5,13 @@
public class AuthTOTPPassword implements AuthRequest.AuthPasswordInterface { public class AuthTOTPPassword implements AuthRequest.AuthPasswordInterface {
public String totp; public String totp;
public AuthTOTPPassword() {
}
public AuthTOTPPassword(String totp) {
this.totp = totp;
}
@Override @Override
public boolean check() { public boolean check() {
return true; return true;

View file

@ -97,10 +97,14 @@ public <T extends WebSocketEvent> void eventHandle(T webSocketEvent) {
processEventHandlers(webSocketEvent); processEventHandlers(webSocketEvent);
} }
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException { public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) {
CompletableFuture<T> result = new CompletableFuture<>(); CompletableFuture<T> result = new CompletableFuture<>();
futureMap.put(request.requestUUID, result); futureMap.put(request.requestUUID, result);
try {
sendObject(request, WebSocketRequest.class); sendObject(request, WebSocketRequest.class);
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
return result; return result;
} }
@ -114,20 +118,16 @@ public void unregisterEventHandler(RequestService.EventHandler handler) {
eventHandlers.remove(handler); eventHandlers.remove(handler);
} }
public <T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException { public <T extends WebSocketEvent> T requestSync(Request<T> request) {
try { try {
return request(request).get(); return request(request).get();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RequestException("Request interrupted"); throw new RequestException("Request interrupted");
} catch (ExecutionException e) { } catch (ExecutionException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof IOException)
throw (IOException) e.getCause();
else {
throw new RequestException(cause); throw new RequestException(cause);
} }
} }
}
@Override @Override
public boolean isClosed() { public boolean isClosed() {

View file

@ -7,8 +7,8 @@
url "https://repo.spring.io/plugins-release/" url "https://repo.spring.io/plugins-release/"
} }
} }
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
jar { jar {
archiveClassifier.set('clean') archiveClassifier.set('clean')

View file

@ -1,5 +1,5 @@
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
dependencies { dependencies {
compileOnly group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] compileOnly group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']

View file

@ -0,0 +1,34 @@
package pro.gravit.launcher.core.api;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.FeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import java.util.HashMap;
import java.util.Map;
public class LauncherAPI {
private final Map<Class<? extends FeatureAPI>, FeatureAPI> map;
public LauncherAPI(Map<Class<? extends FeatureAPI>, FeatureAPI> map) {
this.map = new HashMap<>(map);
}
public AuthFeatureAPI auth() {
return get(AuthFeatureAPI.class);
}
public UserFeatureAPI user() {
return get(UserFeatureAPI.class);
}
public ProfileFeatureAPI profile() {
return get(ProfileFeatureAPI.class);
}
@SuppressWarnings("unchecked")
public<T extends FeatureAPI> T get(Class<T> clazz) {
return (T) map.get(clazz);
}
}

View file

@ -0,0 +1,70 @@
package pro.gravit.launcher.core.api;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public final class LauncherAPIHolder {
private static volatile CoreFeatureAPI coreAPI;
private static volatile LauncherAPI api;
private static volatile Function<String, LauncherAPI> createApiFactory;
private static final Map<String, LauncherAPI> map = new ConcurrentHashMap<>();
public static void setCoreAPI(CoreFeatureAPI coreAPI) {
LauncherAPIHolder.coreAPI = coreAPI;
}
public static void setApi(LauncherAPI api) {
LauncherAPIHolder.api = api;
}
public static void setCreateApiFactory(Function<String, LauncherAPI> createApiFactory) {
LauncherAPIHolder.createApiFactory = createApiFactory;
}
public static void changeAuthId(String authId) {
LauncherAPIHolder.api = map.computeIfAbsent(authId, createApiFactory);
}
public static LauncherAPI get() {
return api;
}
public static LauncherAPI get(String authId) {
return map.computeIfAbsent(authId, createApiFactory);
}
public static CoreFeatureAPI core() {
return coreAPI;
}
public static AuthFeatureAPI auth() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.auth();
}
public static UserFeatureAPI user() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.user();
}
public static ProfileFeatureAPI profile() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.profile();
}
public static void set(LauncherAPI api) {
LauncherAPIHolder.api = api;
}
}

View file

@ -0,0 +1,24 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface AuthFeatureAPI extends FeatureAPI {
CompletableFuture<SelfUser> getCurrentUser();
CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword password);
CompletableFuture<AuthToken> refreshToken(String refreshToken);
CompletableFuture<SelfUser> restore(String accessToken, boolean fetchUser);
CompletableFuture<Void> exit();
record AuthResponse(SelfUser user, AuthToken authToken) {}
interface AuthToken {
String getAccessToken();
String getRefreshToken();
long getExpire();
}
}

View file

@ -0,0 +1,13 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.method.AuthMethod;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface CoreFeatureAPI {
CompletableFuture<List<AuthMethod>> getAuthMethods();
CompletableFuture<LauncherUpdateInfo> checkUpdates();
record LauncherUpdateInfo(String url, String version, boolean available, boolean required) {}
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.features;
public interface FeatureAPI {
}

View file

@ -0,0 +1,43 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.hasher.HashedDir;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface ProfileFeatureAPI extends FeatureAPI {
CompletableFuture<List<ClientProfile>> getProfiles();
CompletableFuture<Void> changeCurrentProfile(ClientProfile profile);
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
interface UpdateInfo {
HashedDir getHashedDir();
String getUrl();
}
interface ClientProfile {
String getName();
UUID getUUID();
String getDescription();
List<OptionalMod> getOptionalMods();
String getProperty(String name);
Map<String, String> getProperties();
ServerInfo getServer();
interface ServerInfo {
String getAddress();
int getPort();
}
}
interface OptionalMod {
String getName();
String getDescription();
String getCategory();
boolean isVisible();
Set<OptionalMod> getDependencies();
}
}

View file

@ -0,0 +1,20 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.model.Texture;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public interface TextureUploadFeatureAPI {
CompletableFuture<TextureUploadInfo> fetchInfo();
CompletableFuture<Texture> upload(String name, byte[] bytes, UploadSettings settings);
interface TextureUploadInfo {
Set<String> getAvailable();
boolean isRequireManualSlimSkinSelect();
}
record UploadSettings(boolean slim) {
}
}

View file

@ -0,0 +1,37 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.model.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public interface UserFeatureAPI extends FeatureAPI {
CompletableFuture<User> getUserByUsername(String username);
CompletableFuture<User> getUserByUUID(UUID uuid);
CompletableFuture<Void> joinServer(String username, String accessToken, String serverID);
CompletableFuture<Void> joinServer(UUID uuid, String accessToken, String serverID);
CompletableFuture<CheckServerResponse> checkServer(String username, String serverID, boolean extended);
default CompletableFuture<List<User>> getUsersByUsernames(List<String> usernames) {
List<CompletableFuture<User>> list = new ArrayList<>();
for(var username : usernames) {
list.add(getUserByUsername(username));
}
return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)).thenApply(x -> {
List<User> r = new ArrayList<>();
for(var e : list) {
try {
r.add(e.get());
} catch (InterruptedException | ExecutionException ex) {
r.add(null);
}
}
return r;
});
}
record CheckServerResponse(User user, String hardwareId, String sessionId, Map<String, String> sessionProperties) {}
}

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.core.api.method;
import java.util.List;
import java.util.Set;
public interface AuthMethod {
List<AuthMethodDetails> getDetails();
String getName();
String getDisplayName();
boolean isVisible();
Set<String> getFeatures();
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.method;
public interface AuthMethodDetails {
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.method;
public interface AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthLoginOnlyDetails implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthPasswordDetails implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public record AuthTotpDetails(int length) implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public record AuthWebDetails(String url, String redirectUrl, boolean externalBrowserSupport) implements AuthMethodDetails {
}

View file

@ -0,0 +1,8 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import java.util.List;
public record AuthChainPassword(List<AuthMethodPassword> list) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthOAuthPassword(String redirectUrl) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthPlainPassword(String value) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthTotpPassword(String value) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.model;
public interface SelfUser extends User {
String getAccessToken();
UserPermissions getPermissions();
}

View file

@ -0,0 +1,9 @@
package pro.gravit.launcher.core.api.model;
import java.util.Map;
public interface Texture {
String getUrl();
String getHash();
Map<String, String> getMetadata();
}

View file

@ -0,0 +1,11 @@
package pro.gravit.launcher.core.api.model;
import java.util.Map;
import java.util.UUID;
public interface User {
String getUsername();
UUID getUUID();
Map<String, Texture> getAssets();
Map<String, String> getProperties();
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.model;
public interface UserPermissions {
boolean hasRole(String role);
boolean hasPerm(String action);
}

View file

@ -0,0 +1,175 @@
package pro.gravit.launcher.core.backend;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.Texture;
import pro.gravit.launcher.core.api.model.UserPermissions;
import pro.gravit.launcher.core.backend.extensions.Extension;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public interface LauncherBackendAPI {
void setCallback(MainCallback callback);
CompletableFuture<LauncherInitData> init();
void selectAuthMethod(AuthMethod method);
CompletableFuture<SelfUser> tryAuthorize();
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();
CompletableFuture<ServerPingInfo> pingServer(ProfileFeatureAPI.ClientProfile profile);
// 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();
boolean isTestMode();
// Extensions
<T extends Extension> T getExtension(Class<T> clazz);
void shutdown();
record LauncherInitData(List<AuthMethod> methods) {}
interface ReadyProfile {
ProfileFeatureAPI.ClientProfile getClientProfile();
ClientProfileSettings getSettings();
void run(RunCallback callback) throws Exception;
}
interface ClientProfileSettings {
long getReservedMemoryBytes(MemoryClass memoryClass);
long getMaxMemoryBytes(MemoryClass memoryClass);
void setReservedMemoryBytes(MemoryClass memoryClass, long value);
Set<Flag> getFlags();
Set<Flag> getAvailableFlags();
boolean hasFlag(Flag flag);
void addFlag(Flag flag);
void removeFlag(Flag flag);
Set<ProfileFeatureAPI.OptionalMod> getEnabledOptionals();
void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
Java getSelectedJava();
Java getRecommendedJava();
void setSelectedJava(Java java);
boolean isRecommended(Java java);
boolean isCompatible(Java java);
ClientProfileSettings copy();
enum Flag {
@LauncherNetworkAPI
AUTO_ENTER,
@LauncherNetworkAPI
FULLSCREEN,
@LauncherNetworkAPI
LINUX_WAYLAND_SUPPORT,
@LauncherNetworkAPI
DEBUG_SKIP_FILE_MONITOR
}
enum MemoryClass {
TOTAL
}
interface ChangedOptionalStatusCallback {
void onChanged(ProfileFeatureAPI.OptionalMod mod, boolean enabled);
}
}
// Callbacks
class MainCallback {
// On any request
public void onChangeStatus(String status) {
}
public void onProfiles(List<ProfileFeatureAPI.ClientProfile> profiles) {
}
public void onAuthorize(SelfUser selfUser) {
}
public void onNotify(String header, String description) {
}
public void onShutdown() {
}
}
class RunCallback {
public void onStarted() {
}
public void onCanTerminate(Runnable terminate) {
}
public void onFinished(int code) {
}
public void onNormalOutput(byte[] buf, int offset, int size) {
}
public void onErrorOutput(byte[] buf, int offset, int size) {
}
}
class DownloadCallback {
public static final String STAGE_ASSET_VERIFY = "assetVerify";
public static final String STAGE_HASHING = "hashing";
public static final String STAGE_DIFF = "diff";
public static final String STAGE_DOWNLOAD = "download";
public static final String STAGE_DELETE_EXTRA = "deleteExtra";
public static final String STAGE_DONE_PART = "done.part";
public static final String STAGE_DONE = "done";
public void onStage(String stage) {
}
public void onCanCancel(Runnable cancel) {
}
public void onTotalDownload(long total) {
}
public void onCurrentDownloaded(long current) {
}
}
interface Java {
int getMajorVersion();
Path getPath();
}
interface ServerPingInfo {
int getMaxOnline();
int getOnline();
List<String> getPlayerNames();
}
}

View file

@ -0,0 +1,13 @@
package pro.gravit.launcher.core.backend;
public class LauncherBackendAPIHolder {
private static volatile LauncherBackendAPI api;
public static LauncherBackendAPI getApi() {
return api;
}
public static void setApi(LauncherBackendAPI api) {
LauncherBackendAPIHolder.api = api;
}
}

View file

@ -1,4 +1,4 @@
package pro.gravit.launcher.runtime.client; package pro.gravit.launcher.core.backend;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;

View file

@ -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);
}
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.backend.extensions;
public interface Extension {
}

View file

@ -0,0 +1,11 @@
package pro.gravit.launcher.core.backend.extensions;
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
import pro.gravit.launcher.core.api.model.Texture;
import java.util.concurrent.CompletableFuture;
public interface TextureUploadExtension extends Extension {
CompletableFuture<TextureUploadFeatureAPI.TextureUploadInfo> fetchTextureUploadInfo();
CompletableFuture<Texture> uploadTexture(String name, byte[] bytes, TextureUploadFeatureAPI.UploadSettings settings);
}

View file

@ -1,5 +1,7 @@
package pro.gravit.utils.helper; package pro.gravit.utils.helper;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; 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 Path jvmDir;
public final int version; public final int version;
public final int build; 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")); Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar"));
return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin); 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;
}
} }
} }

View file

@ -8,8 +8,8 @@
url "https://repo.spring.io/plugins-release/" url "https://repo.spring.io/plugins-release/"
} }
} }
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
jar { jar {
archiveClassifier.set('clean') archiveClassifier.set('clean')

View file

@ -1,4 +1,4 @@
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.gradleup.shadow'
String mainClassName = "pro.gravit.launcher.server.ServerWrapper" String mainClassName = "pro.gravit.launcher.server.ServerWrapper"
String mainAgentName = "pro.gravit.launcher.server.ServerAgent" String mainAgentName = "pro.gravit.launcher.server.ServerAgent"
@ -14,8 +14,8 @@
} }
} }
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
jar { jar {
archiveClassifier.set('clean') archiveClassifier.set('clean')

View file

@ -1,5 +1,5 @@
plugins { plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false id 'com.gradleup.shadow' version '9.0.0-beta15' apply false
id 'maven-publish' id 'maven-publish'
id 'signing' id 'signing'
id 'org.openjfx.javafxplugin' version '0.1.0' apply false id 'org.openjfx.javafxplugin' version '0.1.0' apply false
@ -30,13 +30,6 @@
maven { maven {
url "https://jitpack.io/" url "https://jitpack.io/"
} }
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
} }
jar { jar {
@ -71,6 +64,10 @@
repositories { repositories {
maven { maven {
url = version.endsWith('SNAPSHOT') ? getProperty('mavenSnapshotRepository') : getProperty('mavenReleaseRepository') url = version.endsWith('SNAPSHOT') ? getProperty('mavenSnapshotRepository') : getProperty('mavenReleaseRepository')
credentials {
username getProperty('mavenUsername')
password getProperty('mavenPassword')
}
} }
} }
} }

@ -1 +1 @@
Subproject commit 6f699fae50f98cec19279092d9d5fac1ac451914 Subproject commit acab14e403bb12649883017b14f2cf66cb1f4e68