From f6b3a62497bfcdf68969a7b6f312955d2d9d814a Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:02:52 +0700 Subject: [PATCH 1/9] [FEATURE][EXPERIMENTAL] New API --- .../main/resources/experimental-build.json | 2 +- .../launcher/base/ClientPermissions.java | 5 +- .../base/events/request/AuthRequestEvent.java | 26 ++- .../request/CurrentUserRequestEvent.java | 38 +++- .../GetAvailabilityAuthRequestEvent.java | 41 ++++- .../launcher/base/profiles/ClientProfile.java | 3 +- .../launcher/base/profiles/PlayerProfile.java | 22 ++- .../launcher/base/profiles/Texture.java | 17 +- .../request/RequestCoreFeatureAPIImpl.java | 31 ++++ .../base/request/RequestFeatureAPIImpl.java | 169 ++++++++++++++++++ .../launcher/base/request/RequestService.java | 2 +- .../base/request/auth/AuthRequest.java | 3 +- .../auth/details/AuthLoginOnlyDetails.java | 6 + .../auth/details/AuthPasswordDetails.java | 5 + .../request/auth/details/AuthTotpDetails.java | 6 + .../auth/details/AuthWebViewDetails.java | 7 + .../auth/password/Auth2FAPassword.java | 8 + .../auth/password/AuthTOTPPassword.java | 7 + .../websockets/StdWebSocketService.java | 8 +- .../launcher/core/LauncherBackendAPI.java | 109 +++++++++++ .../gravit/launcher/core/api/LauncherAPI.java | 34 ++++ .../launcher/core/api/LauncherAPIHolder.java | 70 ++++++++ .../core/api/features/AuthFeatureAPI.java | 24 +++ .../core/api/features/CoreFeatureAPI.java | 13 ++ .../core/api/features/FeatureAPI.java | 4 + .../core/api/features/ProfileFeatureAPI.java | 26 +++ .../core/api/features/UserFeatureAPI.java | 37 ++++ .../launcher/core/api/method/AuthMethod.java | 12 ++ .../core/api/method/AuthMethodDetails.java | 4 + .../core/api/method/AuthMethodPassword.java | 4 + .../method/details/AuthLoginOnlyDetails.java | 6 + .../method/details/AuthPasswordDetails.java | 6 + .../api/method/details/AuthTotpDetails.java | 6 + .../api/method/details/AuthWebDetails.java | 6 + .../method/password/AuthChainPassword.java | 8 + .../method/password/AuthOAuthPassword.java | 6 + .../method/password/AuthPlainPassword.java | 6 + .../api/method/password/AuthTotpPassword.java | 6 + .../launcher/core/api/model/SelfUser.java | 6 + .../launcher/core/api/model/Texture.java | 9 + .../gravit/launcher/core/api/model/User.java | 11 ++ .../core/api/model/UserPermissions.java | 6 + .../main/java/pro/gravit/utils/Version.java | 6 +- .../gravit/launcher/server/ServerWrapper.java | 26 ++- 44 files changed, 838 insertions(+), 19 deletions(-) create mode 100644 LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java create mode 100644 LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java diff --git a/LaunchServer/src/main/resources/experimental-build.json b/LaunchServer/src/main/resources/experimental-build.json index ab148d00..8f5e3d65 100644 --- a/LaunchServer/src/main/resources/experimental-build.json +++ b/LaunchServer/src/main/resources/experimental-build.json @@ -1,4 +1,4 @@ { - "features": [], + "features": ["new-api"], "info": [] } \ No newline at end of file diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java index bfa23a50..fdd718b8 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/ClientPermissions.java @@ -1,10 +1,11 @@ package pro.gravit.launcher.base; import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.model.UserPermissions; import java.util.*; -public class ClientPermissions { +public class ClientPermissions implements UserPermissions { public static final ClientPermissions DEFAULT = new ClientPermissions(); @LauncherNetworkAPI private List roles; @@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() { return perm; } + @Override public boolean hasRole(String role) { return roles != null && roles.contains(role); } @@ -46,6 +48,7 @@ public synchronized void compile() { } } + @Override public boolean hasPerm(String action) { if (available == null) { compile(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java index a03007d3..7f6c5f61 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.events.request; import pro.gravit.launcher.base.ClientPermissions; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.PlayerProfile; @@ -67,7 +68,15 @@ public String getType() { return "auth"; } - public static class OAuthRequestEvent { + public CurrentUserRequestEvent.UserInfo makeUserInfo() { + var userInfo = new CurrentUserRequestEvent.UserInfo(); + userInfo.accessToken = accessToken; + userInfo.permissions = permissions; + userInfo.playerProfile = playerProfile; + return userInfo; + } + + public static class OAuthRequestEvent implements AuthFeatureAPI.AuthToken { public final String accessToken; public final String refreshToken; public final long expire; @@ -77,5 +86,20 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) { this.refreshToken = refreshToken; this.expire = expire; } + + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public String getRefreshToken() { + return refreshToken; + } + + @Override + public long getExpire() { + return expire; + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java index f9c67018..3f9f9f3c 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java @@ -3,6 +3,11 @@ import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.PlayerProfile; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.Map; +import java.util.UUID; public class CurrentUserRequestEvent extends RequestEvent { public final UserInfo userInfo; @@ -16,9 +21,40 @@ public String getType() { return "currentUser"; } - public static class UserInfo { + public static class UserInfo implements SelfUser { public ClientPermissions permissions; public String accessToken; public 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(); + } + + @SuppressWarnings("unchecked") + @Override + public Map getAssets() { + return (Map) playerProfile.getAssets(); + } + + @Override + public Map getProperties() { + return playerProfile.getProperties(); + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java index efe0fbca..25169ec4 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/GetAvailabilityAuthRequestEvent.java @@ -1,9 +1,16 @@ package pro.gravit.launcher.base.events.request; +import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails; +import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails; import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.events.RequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; +import pro.gravit.launcher.core.api.method.details.AuthPasswordDetails; +import pro.gravit.launcher.core.api.method.details.AuthWebDetails; import pro.gravit.utils.TypeSerializeInterface; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -37,10 +44,11 @@ public enum ServerFeature { } } - public interface AuthAvailabilityDetails extends TypeSerializeInterface { + public interface AuthAvailabilityDetails extends AuthMethodDetails, TypeSerializeInterface { + AuthMethodDetails toAuthMethodDetails(); } - public static class AuthAvailability { + public static class AuthAvailability implements AuthMethod { public final List details; @LauncherNetworkAPI public String name; @@ -59,5 +67,34 @@ public AuthAvailability(List details, String name, Stri this.visible = visible; this.features = features; } + + @Override + public List getDetails() { + List convert = new ArrayList<>(); + for(var e : details) { + convert.add(e.toAuthMethodDetails()); + } + return convert; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public boolean isVisible() { + return visible; + } + + @Override + public Set getFeatures() { + return features; + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java index 5d55896d..04dcc2f7 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java @@ -6,6 +6,7 @@ import pro.gravit.launcher.base.profiles.optional.OptionalDepend; import pro.gravit.launcher.base.profiles.optional.OptionalFile; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.launch.LaunchOptions; @@ -15,7 +16,7 @@ import java.nio.file.Path; import java.util.*; -public final class ClientProfile implements Comparable { +public final class ClientProfile implements Comparable, ProfileFeatureAPI.ClientProfile { private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher( new String[0], new String[]{"indexes", "objects"}, new String[0]); private transient Path profileFilePath; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java index e0190993..00862e54 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java @@ -1,5 +1,6 @@ package pro.gravit.launcher.base.profiles; +import pro.gravit.launcher.core.api.model.User; import pro.gravit.utils.helper.IOHelper; import java.util.HashMap; @@ -7,7 +8,7 @@ import java.util.Objects; import java.util.UUID; -public final class PlayerProfile { +public final class PlayerProfile implements User { public final UUID uuid; public final String username; @@ -49,4 +50,23 @@ public static UUID offlineUUID(String username) { return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username)); } + @Override + public String getUsername() { + return username; + } + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public Map getAssets() { + return assets; + } + + @Override + public Map getProperties() { + return properties; + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java index 568f1d14..dee9b732 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/Texture.java @@ -14,7 +14,7 @@ import java.util.Arrays; import java.util.Map; -public final class Texture extends StreamObject { +public final class Texture extends StreamObject implements pro.gravit.launcher.core.api.model.Texture { private static final SecurityHelper.DigestAlgorithm DIGEST_ALGO = SecurityHelper.DigestAlgorithm.SHA256; // Instance @@ -85,4 +85,19 @@ public String toString() { ", metadata=" + metadata + '}'; } + + @Override + public String getUrl() { + return url; + } + + @Override + public String getHash() { + return SecurityHelper.toHex(digest); + } + + @Override + public Map getMetadata() { + return metadata; + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java new file mode 100644 index 00000000..66d54a9c --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestCoreFeatureAPIImpl.java @@ -0,0 +1,31 @@ +package pro.gravit.launcher.base.request; + +import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest; +import pro.gravit.launcher.base.request.update.LauncherRequest; +import pro.gravit.launcher.core.api.features.CoreFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethod; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class RequestCoreFeatureAPIImpl implements CoreFeatureAPI { + private final RequestService request; + + public RequestCoreFeatureAPIImpl(RequestService request) { + this.request = request; + } + + + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getAuthMethods() { + return request.request(new GetAvailabilityAuthRequest()).thenApply(response -> (List) response.list); + } + + @Override + public CompletableFuture checkUpdates() { + return request.request(new LauncherRequest()).thenApply(response -> new LauncherUpdateInfo(response.url, + "Unknown", response.needUpdate, response.needUpdate)); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java new file mode 100644 index 00000000..b8a9d53c --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -0,0 +1,169 @@ +package pro.gravit.launcher.base.request; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.request.auth.*; +import pro.gravit.launcher.base.request.auth.password.*; +import pro.gravit.launcher.base.request.update.ProfilesRequest; +import pro.gravit.launcher.base.request.update.UpdateRequest; +import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest; +import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.method.password.AuthChainPassword; +import pro.gravit.launcher.core.api.method.password.AuthOAuthPassword; +import pro.gravit.launcher.core.api.method.password.AuthPlainPassword; +import pro.gravit.launcher.core.api.method.password.AuthTotpPassword; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.User; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.utils.helper.SecurityHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI { + private final RequestService request; + private final String authId; + + public RequestFeatureAPIImpl(RequestService request, String authId) { + this.request = request; + this.authId = authId; + } + + @Override + public CompletableFuture getCurrentUser() { + return request.request(new CurrentUserRequest()).thenApply(response -> response.userInfo); + } + + @Override + public CompletableFuture auth(String login, AuthMethodPassword password) { + return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, AuthRequest.ConnectTypes.CLIENT)) + .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 { + throw new UnsupportedOperationException(); + } + } + + @Override + public CompletableFuture getUserByUsername(String username) { + return request.request(new ProfileByUsernameRequest(username)).thenApply(response -> response.playerProfile); + } + + @Override + public CompletableFuture getUserByUUID(UUID uuid) { + return request.request(new ProfileByUUIDRequest(uuid)).thenApply(response -> response.playerProfile); + } + + @Override + public CompletableFuture joinServer(String username, String accessToken, String serverID) { + return request.request(new JoinServerRequest(username, accessToken, serverID)).thenCompose(response -> { + if(response.allow) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new RequestException("Not allowed")); + } + }); + } + + @Override + public CompletableFuture joinServer(UUID uuid, String accessToken, String serverID) { + return request.request(new JoinServerRequest(uuid, accessToken, serverID)).thenCompose(response -> { + if(response.allow) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new RequestException("Not allowed")); + } + }); + } + + @Override + public CompletableFuture checkServer(String username, String serverID, boolean extended) { + return request.request(new CheckServerRequest(username, serverID, extended, extended)) + .thenApply(response -> new CheckServerResponse(response.playerProfile, response.hardwareId, + response.sessionId, response.sessionProperties)); + } + + @Override + public CompletableFuture refreshToken(String refreshToken) { + return request.request(new RefreshTokenRequest(authId, refreshToken)).thenApply(response -> response.oauth); + } + + @Override + public CompletableFuture restore(String accessToken, boolean fetchUser) { + Map extended = new HashMap<>(); + if(Request.getExtendedTokens() != null) { // TODO: Control extended token + for(var e : Request.getExtendedTokens().entrySet()) { + extended.put(e.getKey(), e.getValue().token); + } + } + return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> { + // TODO: invalidToken process + return e.userInfo; + }); + } + + @Override + public CompletableFuture exit() { + return request.request(new ExitRequest()).thenApply(response -> null); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getProfiles() { + return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles); + } + + @Override + public CompletableFuture 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 {} +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java index bad2ebd1..643c98f1 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestService.java @@ -5,7 +5,7 @@ import java.util.concurrent.ExecutionException; public interface RequestService { - CompletableFuture request(Request request) throws IOException; + CompletableFuture request(Request request); void open() throws Exception; void registerEventHandler(EventHandler handler); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java index 5cbc489f..59ab371b 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/AuthRequest.java @@ -5,6 +5,7 @@ import pro.gravit.launcher.base.request.Request; import pro.gravit.launcher.base.request.auth.password.*; import pro.gravit.launcher.base.request.websockets.WebSocketRequest; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; import pro.gravit.utils.ProviderMap; public final class AuthRequest extends Request implements WebSocketRequest { @@ -64,7 +65,7 @@ public enum ConnectTypes { API } - public interface AuthPasswordInterface { + public interface AuthPasswordInterface extends AuthMethodPassword { boolean check(); default boolean isAllowSave() { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java index a5813a17..68630ac3 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthLoginOnlyDetails.java @@ -1,10 +1,16 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthLoginOnlyDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { @Override public String getType() { return "loginonly"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthLoginOnlyDetails(); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java index 825fa36f..2cbc66bb 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthPasswordDetails.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthPasswordDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { @Override @@ -9,4 +10,8 @@ public String getType() { } + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthPasswordDetails(); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java index 80c4048e..fdbce796 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthTotpDetails.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; public class AuthTotpDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public final String alg; @@ -20,4 +21,9 @@ public AuthTotpDetails(String alg) { public String getType() { return "totp"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new pro.gravit.launcher.core.api.method.details.AuthTotpDetails(maxKeyLength); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java index 3090d251..8b4a6593 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/details/AuthWebViewDetails.java @@ -1,6 +1,8 @@ package pro.gravit.launcher.base.request.auth.details; import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.core.api.method.AuthMethodDetails; +import pro.gravit.launcher.core.api.method.details.AuthWebDetails; public class AuthWebViewDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails { public final String url; @@ -26,4 +28,9 @@ public AuthWebViewDetails(String url, String redirectUrl) { public String getType() { return "webview"; } + + @Override + public AuthMethodDetails toAuthMethodDetails() { + return new AuthWebDetails(url, redirectUrl, canBrowser); + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java index 05672942..9732d69f 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/Auth2FAPassword.java @@ -6,6 +6,14 @@ public class Auth2FAPassword implements AuthRequest.AuthPasswordInterface { public AuthRequest.AuthPasswordInterface firstPassword; public AuthRequest.AuthPasswordInterface secondPassword; + public Auth2FAPassword() { + } + + public Auth2FAPassword(AuthRequest.AuthPasswordInterface firstPassword, AuthRequest.AuthPasswordInterface secondPassword) { + this.firstPassword = firstPassword; + this.secondPassword = secondPassword; + } + @Override public boolean check() { return firstPassword != null && firstPassword.check() && secondPassword != null && secondPassword.check(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java index 4ddccec6..00c2aec1 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/auth/password/AuthTOTPPassword.java @@ -5,6 +5,13 @@ public class AuthTOTPPassword implements AuthRequest.AuthPasswordInterface { public String totp; + public AuthTOTPPassword() { + } + + public AuthTOTPPassword(String totp) { + this.totp = totp; + } + @Override public boolean check() { return true; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java index fe6dade9..76c979b0 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/websockets/StdWebSocketService.java @@ -98,10 +98,14 @@ public void eventHandle(T webSocketEvent) { processEventHandlers(webSocketEvent); } - public CompletableFuture request(Request request) throws IOException { + public CompletableFuture request(Request request) { CompletableFuture result = new CompletableFuture<>(); futureMap.put(request.requestUUID, result); - sendObject(request, WebSocketRequest.class); + try { + sendObject(request, WebSocketRequest.class); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } return result; } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java new file mode 100644 index 00000000..f41dbab0 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java @@ -0,0 +1,109 @@ +package pro.gravit.launcher.core; + +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 java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface LauncherBackendAPI { + void setCallback(MainCallback callback); + CompletableFuture init(); + void selectAuthMethod(AuthMethod method); + CompletableFuture tryAuthorize(); + CompletableFuture authorize(String login, AuthMethodPassword password); + CompletableFuture> fetchProfiles(); + ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile); + CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback); + // Tools + CompletableFuture fetchTexture(Texture texture); + + record LauncherInitData(List methods) {} + + interface ReadyProfile { + ProfileFeatureAPI.ClientProfile getClientProfile(); + ClientProfileSettings getSettings(); + void run(RunCallback callback) throws Exception; + } + + interface ClientProfileSettings { + long getReservedMemoryBytes(); + long getMaxMemoryBytes(); + void setReservedMemoryBytes(long value); + List getFlags(); + boolean hasFlag(Flag flag); + void addFlag(Flag flag); + void removeFlag(Flag flag); + List getEnabledOptionals(); + void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); + void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); + + enum Flag { + + } + + interface ChangedOptionalStatusCallback { + void onChanged(ProfileFeatureAPI.OptionalMod mod, boolean enabled); + } + } + + // Callbacks + + class MainCallback { + // On any request + public void onChangeStatus(String status) { + + } + + public void onProfiles(List profiles) { + + } + + public void onAuthorize(SelfUser selfUser) { + + } + + public void onNotify(String header, String description) { + + } + } + + class RunCallback { + public void onStarted() { + + } + + 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 void onStage(String stage) { + + } + + public void onCanCancel(Runnable cancel) { + + } + + public void onTotalDownload(long total) { + + } + + public void onCurrentDownloaded(long current) { + + } + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java new file mode 100644 index 00000000..206c27d7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPI.java @@ -0,0 +1,34 @@ +package pro.gravit.launcher.core.api; + +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.FeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; + +import java.util.HashMap; +import java.util.Map; + +public class LauncherAPI { + private final Map, FeatureAPI> map; + + public LauncherAPI(Map, FeatureAPI> map) { + this.map = new HashMap<>(map); + } + + public AuthFeatureAPI auth() { + return get(AuthFeatureAPI.class); + } + + public UserFeatureAPI user() { + return get(UserFeatureAPI.class); + } + + public ProfileFeatureAPI profile() { + return get(ProfileFeatureAPI.class); + } + + @SuppressWarnings("unchecked") + public T get(Class clazz) { + return (T) map.get(clazz); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java new file mode 100644 index 00000000..048281e7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/LauncherAPIHolder.java @@ -0,0 +1,70 @@ +package pro.gravit.launcher.core.api; + +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.CoreFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public final class LauncherAPIHolder { + private static volatile CoreFeatureAPI coreAPI; + private static volatile LauncherAPI api; + private static volatile Function createApiFactory; + private static final Map map = new ConcurrentHashMap<>(); + + public static void setCoreAPI(CoreFeatureAPI coreAPI) { + LauncherAPIHolder.coreAPI = coreAPI; + } + + public static void setApi(LauncherAPI api) { + LauncherAPIHolder.api = api; + } + + public static void setCreateApiFactory(Function createApiFactory) { + LauncherAPIHolder.createApiFactory = createApiFactory; + } + + public static void changeAuthId(String authId) { + LauncherAPIHolder.api = map.computeIfAbsent(authId, createApiFactory); + } + + public static LauncherAPI get() { + return api; + } + + public static LauncherAPI get(String authId) { + return map.computeIfAbsent(authId, createApiFactory); + } + + public static CoreFeatureAPI core() { + return coreAPI; + } + + public static AuthFeatureAPI auth() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.auth(); + } + + public static UserFeatureAPI user() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.user(); + } + + public static ProfileFeatureAPI profile() { + if(api == null) { + throw new UnsupportedOperationException(); + } + return api.profile(); + } + + public static void set(LauncherAPI api) { + LauncherAPIHolder.api = api; + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java new file mode 100644 index 00000000..04375268 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/AuthFeatureAPI.java @@ -0,0 +1,24 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.model.SelfUser; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface AuthFeatureAPI extends FeatureAPI { + CompletableFuture getCurrentUser(); + CompletableFuture auth(String login, AuthMethodPassword password); + CompletableFuture refreshToken(String refreshToken); + CompletableFuture restore(String accessToken, boolean fetchUser); + CompletableFuture exit(); + + record AuthResponse(SelfUser user, AuthToken authToken) {} + + interface AuthToken { + String getAccessToken(); + String getRefreshToken(); + long getExpire(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java new file mode 100644 index 00000000..1b1c241f --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/CoreFeatureAPI.java @@ -0,0 +1,13 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.method.AuthMethod; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface CoreFeatureAPI { + CompletableFuture> getAuthMethods(); + CompletableFuture checkUpdates(); + + record LauncherUpdateInfo(String url, String version, boolean available, boolean required) {} +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java new file mode 100644 index 00000000..2a71e92c --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/FeatureAPI.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.features; + +public interface FeatureAPI { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java new file mode 100644 index 00000000..b751de40 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -0,0 +1,26 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.hasher.HashedDir; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface ProfileFeatureAPI extends FeatureAPI { + CompletableFuture> getProfiles(); + CompletableFuture fetchUpdateInfo(String dirName); + + interface UpdateInfo {} + + interface ClientProfile { + String getName(); + UUID getUUID(); + List getOptionalMods(); + } + + interface OptionalMod { + String getName(); + String getDescription(); + String getCategory(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java new file mode 100644 index 00000000..7fec7265 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/UserFeatureAPI.java @@ -0,0 +1,37 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.model.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public interface UserFeatureAPI extends FeatureAPI { + CompletableFuture getUserByUsername(String username); + CompletableFuture getUserByUUID(UUID uuid); + CompletableFuture joinServer(String username, String accessToken, String serverID); + CompletableFuture joinServer(UUID uuid, String accessToken, String serverID); + CompletableFuture checkServer(String username, String serverID, boolean extended); + default CompletableFuture> getUsersByUsernames(List usernames) { + List> list = new ArrayList<>(); + for(var username : usernames) { + list.add(getUserByUsername(username)); + } + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)).thenApply(x -> { + List r = new ArrayList<>(); + for(var e : list) { + try { + r.add(e.get()); + } catch (InterruptedException | ExecutionException ex) { + r.add(null); + } + } + return r; + }); + } + + record CheckServerResponse(User user, String hardwareId, String sessionId, Map sessionProperties) {} +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java new file mode 100644 index 00000000..e4881074 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethod.java @@ -0,0 +1,12 @@ +package pro.gravit.launcher.core.api.method; + +import java.util.List; +import java.util.Set; + +public interface AuthMethod { + List getDetails(); + String getName(); + String getDisplayName(); + boolean isVisible(); + Set getFeatures(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java new file mode 100644 index 00000000..27cbed00 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodDetails.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.method; + +public interface AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java new file mode 100644 index 00000000..de8854d1 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/AuthMethodPassword.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.api.method; + +public interface AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java new file mode 100644 index 00000000..535d5d3f --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthLoginOnlyDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public class AuthLoginOnlyDetails implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java new file mode 100644 index 00000000..36e519a4 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthPasswordDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public class AuthPasswordDetails implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java new file mode 100644 index 00000000..ee4f1899 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthTotpDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public record AuthTotpDetails(int length) implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java new file mode 100644 index 00000000..6dba7293 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/details/AuthWebDetails.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.details; + +import pro.gravit.launcher.core.api.method.AuthMethodDetails; + +public record AuthWebDetails(String url, String redirectUrl, boolean externalBrowserSupport) implements AuthMethodDetails { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java new file mode 100644 index 00000000..f95f7773 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthChainPassword.java @@ -0,0 +1,8 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +import java.util.List; + +public record AuthChainPassword(List list) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java new file mode 100644 index 00000000..40af34e0 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthOAuthPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthOAuthPassword(String redirectUrl) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java new file mode 100644 index 00000000..d0d1fbf7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthPlainPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthPlainPassword(String value) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java new file mode 100644 index 00000000..e63f4f37 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/method/password/AuthTotpPassword.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.method.password; + +import pro.gravit.launcher.core.api.method.AuthMethodPassword; + +public record AuthTotpPassword(String value) implements AuthMethodPassword { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java new file mode 100644 index 00000000..3345be64 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/SelfUser.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.model; + +public interface SelfUser extends User { + String getAccessToken(); + UserPermissions getPermissions(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java new file mode 100644 index 00000000..4fd79f98 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/Texture.java @@ -0,0 +1,9 @@ +package pro.gravit.launcher.core.api.model; + +import java.util.Map; + +public interface Texture { + String getUrl(); + String getHash(); + Map getMetadata(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java new file mode 100644 index 00000000..5d1fbd07 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/User.java @@ -0,0 +1,11 @@ +package pro.gravit.launcher.core.api.model; + +import java.util.Map; +import java.util.UUID; + +public interface User { + String getUsername(); + UUID getUUID(); + Map getAssets(); + Map getProperties(); +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java new file mode 100644 index 00000000..6f6520e8 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/model/UserPermissions.java @@ -0,0 +1,6 @@ +package pro.gravit.launcher.core.api.model; + +public interface UserPermissions { + boolean hasRole(String role); + boolean hasPerm(String action); +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/Version.java b/LauncherCore/src/main/java/pro/gravit/utils/Version.java index 6c9a7360..60b56f14 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/Version.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/Version.java @@ -5,10 +5,10 @@ public final class Version implements Comparable { public static final int MAJOR = 5; - public static final int MINOR = 6; - public static final int PATCH = 3; + public static final int MINOR = 7; + public static final int PATCH = 0; public static final int BUILD = 1; - public static final Version.Type RELEASE = Type.DEV; + public static final Version.Type RELEASE = Type.EXPERIMENTAL; public final int major; public final int minor; public final int patch; diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java index a1c7e6d5..082cbf7b 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java @@ -29,10 +29,13 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Type; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class ServerWrapper extends JsonConfigurable { public static final Path configFile = Paths.get(System.getProperty("serverwrapper.configFile", "ServerWrapperConfig.json")); @@ -187,14 +190,30 @@ public void run(String... args) throws Throwable { System.load(Paths.get(config.nativesDir).resolve(ClientService.findLibrary(e)).toAbsolutePath().toString()); } } + List classpath = config.classpath; + if(config.resolveClassPath) { + classpath = new ArrayList<>(); + for(var cp : config.classpath) { + Path p = Paths.get(cp); + if(Files.isDirectory(p)) { + try(Stream files = Files.walk(p, FileVisitOption.FOLLOW_LINKS)) { + var resolved = files.filter(e -> !Files.isDirectory(e)) + .filter(e -> e.getFileName().toString().endsWith(".jar")).map(e -> e.toAbsolutePath().toString()).toList(); + classpath.addAll(resolved); + } + } else { + classpath.add(p.toAbsolutePath().toString()); + } + } + } switch (config.classLoaderConfig) { case LAUNCHER: launch = new LegacyLaunch(); - System.setProperty("java.class.path", String.join(File.pathSeparator, config.classpath)); + System.setProperty("java.class.path", String.join(File.pathSeparator, classpath)); break; case MODULE: launch = new ModuleLaunch(); - System.setProperty("java.class.path", String.join(File.pathSeparator, config.classpath)); + System.setProperty("java.class.path", String.join(File.pathSeparator, classpath)); break; default: if(ServerAgent.isAgentStarted()) { @@ -207,7 +226,7 @@ public void run(String... args) throws Throwable { LaunchOptions options = new LaunchOptions(); options.enableHacks = config.enableHacks; options.moduleConf = config.moduleConf; - classLoaderControl = launch.init(config.classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options); + classLoaderControl = launch.init(classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options); if(ServerAgent.isAgentStarted()) { ClientService.instrumentation = ServerAgent.inst; } @@ -269,6 +288,7 @@ public static final class Config { public String address; public String serverName; public boolean autoloadLibraries; + public boolean resolveClassPath = true; public String logFile; public List classpath; public ClientProfile.ClassLoaderConfig classLoaderConfig; From 3afe77bf34eb7045359bd82665dda95e65536c9f Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:21:25 +0700 Subject: [PATCH 2/9] [FEATURE][EXPERIMENTAL] New API: TextureUploadExtension --- .../launcher/base/profiles/ClientProfile.java | 16 +++++++++++++++ .../base/profiles/optional/OptionalFile.java | 13 +++++++++++- .../core/api/features/ProfileFeatureAPI.java | 5 +++++ .../api/features/TextureUploadFeatureAPI.java | 20 +++++++++++++++++++ .../{ => backend}/LauncherBackendAPI.java | 10 +++++++++- .../core/backend/extensions/Extension.java | 4 ++++ .../extensions/TextureUploadExtension.java | 11 ++++++++++ 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java rename LauncherCore/src/main/java/pro/gravit/launcher/core/{ => backend}/LauncherBackendAPI.java (89%) create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java index 04dcc2f7..137cd961 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java @@ -15,6 +15,7 @@ import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; public final class ClientProfile implements Comparable, ProfileFeatureAPI.ClientProfile { private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher( @@ -287,10 +288,25 @@ public String toString() { return String.format("%s (%s)", title, uuid); } + @Override + public String getName() { + return title; + } + public UUID getUUID() { return uuid; } + @Override + public String getDescription() { + return info; + } + + @Override + public List getOptionalMods() { + return updateOptional.stream().collect(Collectors.toUnmodifiableList()); + } + public boolean hasFlag(CompatibilityFlags flag) { return flags.contains(flag); } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java index 4d49cf23..bf8e261c 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java @@ -2,11 +2,12 @@ import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; import java.util.List; import java.util.Objects; -public class OptionalFile { +public class OptionalFile implements ProfileFeatureAPI.OptionalMod { @LauncherNetworkAPI public List actions; @LauncherNetworkAPI @@ -57,6 +58,16 @@ public String getName() { return name; } + @Override + public String getDescription() { + return info; + } + + @Override + public String getCategory() { + return category; + } + public boolean isVisible() { return visible; } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java index b751de40..a030d5da 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -3,6 +3,7 @@ import pro.gravit.launcher.core.hasher.HashedDir; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -15,12 +16,16 @@ interface UpdateInfo {} interface ClientProfile { String getName(); UUID getUUID(); + String getDescription(); List getOptionalMods(); + String getProperty(String name); + Map getProperties(); } interface OptionalMod { String getName(); String getDescription(); String getCategory(); + boolean isVisible(); } } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java new file mode 100644 index 00000000..cba41c1a --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/TextureUploadFeatureAPI.java @@ -0,0 +1,20 @@ +package pro.gravit.launcher.core.api.features; + +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public interface TextureUploadFeatureAPI { + CompletableFuture fetchInfo(); + CompletableFuture upload(String name, byte[] bytes, UploadSettings settings); + + interface TextureUploadInfo { + Set getAvailable(); + boolean isRequireManualSlimSkinSelect(); + } + + record UploadSettings(boolean slim) { + + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java similarity index 89% rename from LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java rename to LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java index f41dbab0..ef0fd817 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -1,10 +1,12 @@ -package pro.gravit.launcher.core; +package pro.gravit.launcher.core.backend; 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.util.List; import java.util.concurrent.CompletableFuture; @@ -20,6 +22,12 @@ public interface LauncherBackendAPI { CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback); // Tools CompletableFuture fetchTexture(Texture texture); + // Status + UserPermissions getPermissions(); + boolean hasPermission(String permission); + String getUsername(); + // Extensions + T getExtension(Class clazz); record LauncherInitData(List methods) {} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java new file mode 100644 index 00000000..7597c51a --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/Extension.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.core.backend.extensions; + +public interface Extension { +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java new file mode 100644 index 00000000..bab90ae7 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/extensions/TextureUploadExtension.java @@ -0,0 +1,11 @@ +package pro.gravit.launcher.core.backend.extensions; + +import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI; +import pro.gravit.launcher.core.api.model.Texture; + +import java.util.concurrent.CompletableFuture; + +public interface TextureUploadExtension extends Extension { + CompletableFuture fetchTextureUploadInfo(); + CompletableFuture uploadTexture(String name, byte[] bytes, TextureUploadFeatureAPI.UploadSettings settings); +} From 44e840734cb4724ba3b2d9c3dcce6841732c24f8 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:49:34 +0700 Subject: [PATCH 3/9] [FIX] Compile fix --- .../base/events/request/CurrentUserRequestEvent.java | 3 +-- .../pro/gravit/launcher/base/profiles/PlayerProfile.java | 5 +++-- .../pro/gravit/launcher/core/backend/LauncherBackendAPI.java | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java index 3f9f9f3c..fbb40ddd 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/CurrentUserRequestEvent.java @@ -46,10 +46,9 @@ public UUID getUUID() { return playerProfile.getUUID(); } - @SuppressWarnings("unchecked") @Override public Map getAssets() { - return (Map) playerProfile.getAssets(); + return playerProfile.getAssets(); } @Override diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java index 00862e54..083bc4d4 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java @@ -60,9 +60,10 @@ public UUID getUUID() { return uuid; } + @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public Map getAssets() { - return assets; + public Map getAssets() { + return (Map) assets; } @Override diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java index ef0fd817..36a01f49 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -48,6 +48,7 @@ interface ClientProfileSettings { List getEnabledOptionals(); void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); + ClientProfileSettings clone(); enum Flag { From 6b873b50727f2f6ff9c948af960504685559ccc8 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:59:47 +0700 Subject: [PATCH 4/9] [FEATURE][EXPERIMENTAL] New API: Launcher Backend --- .../launcher/runtime/NewLauncherSettings.java | 2 +- .../runtime/backend/BackendSettings.java | 22 ++ .../runtime/backend/ClientDownloadImpl.java | 253 ++++++++++++++++++ .../runtime/backend/LauncherBackendImpl.java | 248 +++++++++++++++++ .../runtime/backend/ProfileSettingsImpl.java | 173 ++++++++++++ .../runtime/backend/ReadyProfileImpl.java | 128 +++++++++ .../runtime/client/RuntimeGsonManager.java | 1 + .../runtime/utils/AssetIndexHelper.java | 65 +++++ .../launcher/runtime/utils/SystemMemory.java | 10 + .../pro/gravit/launcher/base/Downloader.java | 7 +- .../launcher/base/profiles/PlayerProfile.java | 8 + .../base/request/RequestFeatureAPIImpl.java | 12 +- .../core/api/features/ProfileFeatureAPI.java | 5 +- .../core/backend/LauncherBackendAPI.java | 60 ++++- .../launcher/core/backend}/UserSettings.java | 2 +- .../exceptions/LauncherBackendException.java | 14 + .../pro/gravit/utils/helper/JavaHelper.java | 14 +- build.gradle | 2 +- 18 files changed, 1013 insertions(+), 13 deletions(-) create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java rename {Launcher/src/main/java/pro/gravit/launcher/runtime/client => LauncherCore/src/main/java/pro/gravit/launcher/core/backend}/UserSettings.java (77%) create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java index a58108ac..b29f1b5d 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/NewLauncherSettings.java @@ -1,6 +1,6 @@ package pro.gravit.launcher.runtime; -import pro.gravit.launcher.runtime.client.UserSettings; +import pro.gravit.launcher.core.backend.UserSettings; import pro.gravit.launcher.core.LauncherNetworkAPI; import java.util.HashMap; diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java new file mode 100644 index 00000000..1d64ee03 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java @@ -0,0 +1,22 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.backend.UserSettings; + +import java.util.Map; +import java.util.UUID; + +public class BackendSettings extends UserSettings { + @LauncherNetworkAPI + public AuthorizationData auth; + @LauncherNetworkAPI + public Map settings; + public static class AuthorizationData { + @LauncherNetworkAPI + public String accessToken; + @LauncherNetworkAPI + public String refreshToken; + @LauncherNetworkAPI + public long expireIn; + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java new file mode 100644 index 00000000..70601963 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java @@ -0,0 +1,253 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Downloader; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.optional.OptionalView; +import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; +import pro.gravit.launcher.base.profiles.optional.actions.OptionalActionFile; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.core.hasher.FileNameMatcher; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launcher.core.hasher.HashedEntry; +import pro.gravit.launcher.core.hasher.HashedFile; +import pro.gravit.launcher.runtime.client.DirBridge; +import pro.gravit.launcher.runtime.utils.AssetIndexHelper; +import pro.gravit.utils.helper.LogHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +public class ClientDownloadImpl { + private LauncherBackendImpl backend; + + ClientDownloadImpl(LauncherBackendImpl backend) { + this.backend = backend; + } + + CompletableFuture downloadProfile(ClientProfile profile, ProfileSettingsImpl settings, LauncherBackendAPI.DownloadCallback callback) { + AtomicReference clientRef = new AtomicReference<>(); + AtomicReference assetRef = new AtomicReference<>(); + AtomicReference javaRef = new AtomicReference<>(); + return downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback).thenCompose((clientDir -> { + clientRef.set(clientDir); + return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback); + })).thenCompose(assetDir -> { + assetRef.set(assetDir); + return CompletableFuture.completedFuture((DownloadedDir)null); // TODO Custom Java + }).thenCompose(javaDir -> { + javaRef.set(javaDir); + return CompletableFuture.completedFuture(null); + }).thenApply(v -> { + return new ReadyProfileImpl(backend, profile, settings, clientRef.get(), assetRef.get(), javaRef.get()); + }); + } + + CompletableFuture downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> { + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY); + return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir); + }, backend.executorService) + .thenApply(assetData -> { + HashedDir dir = assetData.updateInfo.getHashedDir(); + AssetIndexHelper.modifyHashedDir(assetData.index, dir); + return new VirtualUpdateInfo(dir, assetData.updateInfo.getUrl()); + }) + .thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e)); + } + + private CompletableFuture verifyAssetIndex(String assetIndexString, ProfileFeatureAPI.UpdateInfo response, Path assetIndexPath, Path targetDir) { + var assetIndexRelPath = String.format("indexes/%s.json", assetIndexString); + var assetIndexHash = response.getHashedDir().findRecursive(assetIndexRelPath); + if(!(assetIndexHash.entry instanceof HashedFile assetIndexHashFile)) { + return CompletableFuture.failedFuture(new FileNotFoundException(String.format("Asset Index %s not found in the server response", assetIndexString))); + } + try { + if(Files.exists(assetIndexPath) && assetIndexHashFile.isSame(assetIndexPath, true)) { + var assetIndex = AssetIndexHelper.parse(assetIndexPath); + return CompletableFuture.completedFuture(new AssetData(response, assetIndex)); + } else { + var downloader = Downloader.newDownloader(backend.executorService); + var list = new LinkedList(); + list.add(new Downloader.SizedFile(assetIndexRelPath, assetIndexRelPath, assetIndexHashFile.size)); + return downloader.downloadFiles(list, response.getUrl(), targetDir, null, backend.executorService, 1).thenComposeAsync(v -> { + try { + var assetIndex = AssetIndexHelper.parse(assetIndexPath); + return CompletableFuture.completedFuture(new AssetData(response, assetIndex)); + } catch (IOException e) { + return CompletableFuture.failedFuture(e); + } + }, backend.executorService); + } + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + CompletableFuture downloadDir(String dirName, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName) + .thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e)); + } + + CompletableFuture downloadDir(String dirName, FileNameMatcher matcher, OptionalView view, LauncherBackendAPI.DownloadCallback callback) { + Path targetDir = DirBridge.dirUpdates.resolve(dirName); + return LauncherAPIHolder.profile().fetchUpdateInfo(dirName) + .thenCompose(response -> { + var hashedDir = response.getHashedDir(); + var remap = applyOptionalMods(view, hashedDir); + return downloadDir(targetDir, new VirtualUpdateInfo(hashedDir, response.getUrl()), matcher, callback, makePathRemapperFunction(remap)); + }); + } + + CompletableFuture downloadDir(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback, Function remap) { + return CompletableFuture.supplyAsync(() -> { + try { + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING); + HashedDir realFiles = new HashedDir(targetDir, matcher, false, true); + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF); + return realFiles.diff(updateInfo.getHashedDir(), matcher); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, backend.executorService).thenComposeAsync((diff) -> { + return downloadFiles(targetDir, updateInfo, callback, diff, remap); + }, backend.executorService).thenApply(v -> new DownloadedDir(updateInfo.getHashedDir(), targetDir)); + } + + private CompletableFuture downloadFiles(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, LauncherBackendAPI.DownloadCallback callback, HashedDir.Diff diff, Function remap) { + Downloader downloader = Downloader.newDownloader(backend.executorService); + try { + var files = collectFilesAndCreateDirectories(targetDir, diff.mismatch, remap); + long total = 0; + for(var e : files) { + total += e.size; + } + callback.onTotalDownload(total); + callback.onCanCancel(downloader::cancel); + return downloader.downloadFiles(files, updateInfo.getUrl(), targetDir, new Downloader.DownloadCallback() { + @Override + public void apply(long fullDiff) { + callback.onCurrentDownloaded(fullDiff); + } + + @Override + public void onComplete(Path path) { + + } + }, backend.executorService, 4).thenComposeAsync(v -> { + callback.onCanCancel(null); + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DELETE_EXTRA); + try { + deleteExtraDir(targetDir, diff.extra, diff.extra.flag); + } catch (IOException ex) { + return CompletableFuture.failedFuture(ex); + } + callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DONE_PART); + return CompletableFuture.completedFuture(diff); + }, backend.executorService); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + private List collectFilesAndCreateDirectories(Path dir, HashedDir mismatch, Function pathRemapper) throws IOException { + List files = new ArrayList<>(); + mismatch.walk(File.separator, (path, name, entry) -> { + if(entry.getType() == HashedEntry.Type.DIR) { + Files.createDirectory(dir.resolve(path)); + return HashedDir.WalkAction.CONTINUE; + } + String pathFixed = path.replace(File.separatorChar, '/'); + files.add(new Downloader.SizedFile(pathFixed, pathRemapper.apply(pathFixed), entry.size())); + return HashedDir.WalkAction.CONTINUE; + }); + return files; + } + + private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean deleteDir) throws IOException { + for (Map.Entry mapEntry : subHDir.map().entrySet()) { + String name = mapEntry.getKey(); + Path path = subDir.resolve(name); + + // Delete list and dirs based on type + HashedEntry entry = mapEntry.getValue(); + HashedEntry.Type entryType = entry.getType(); + switch (entryType) { + case FILE -> Files.delete(path); + case DIR -> deleteExtraDir(path, (HashedDir) entry, deleteDir || entry.flag); + default -> throw new AssertionError("Unsupported hashed entry type: " + entryType.name()); + } + } + + // Delete! + if (deleteDir) { + Files.delete(subDir); + } + } + + private Function makePathRemapperFunction(LinkedList map) { + return (path) -> { + for(var e : map) { + if(path.startsWith(e.key)) { + return e.value; + } + } + return path; + }; + } + + private LinkedList applyOptionalMods(OptionalView view, HashedDir hdir) { + for (OptionalAction action : view.getDisabledActions()) { + if (action instanceof OptionalActionFile optionalActionFile) { + optionalActionFile.disableInHashedDir(hdir); + } + } + LinkedList pathRemapper = new LinkedList<>(); + Set fileActions = view.getActionsByClass(OptionalActionFile.class); + for (OptionalActionFile file : fileActions) { + file.injectToHashedDir(hdir); + file.files.forEach((k, v) -> { + if (v == null || v.isEmpty()) return; + pathRemapper.add(new PathRemapperData(v, k)); //reverse (!) + LogHelper.dev("Remap prepare %s to %s", v, k); + }); + } + pathRemapper.sort(Comparator.comparingInt(c -> -c.key.length())); // Support deep remap + return pathRemapper; + } + + private record PathRemapperData(String key, String value) { + } + + record AssetData(ProfileFeatureAPI.UpdateInfo updateInfo, AssetIndexHelper.AssetIndex index) { + + } + + record DownloadedDir(HashedDir dir, Path path) { + + } + + record VirtualUpdateInfo(HashedDir dir, String url) implements ProfileFeatureAPI.UpdateInfo { + + @Override + public HashedDir getHashedDir() { + return dir; + } + + @Override + public String getUrl() { + return url; + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java new file mode 100644 index 00000000..cfd85d0b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java @@ -0,0 +1,248 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.ClientPermissions; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.method.AuthMethod; +import pro.gravit.launcher.core.api.method.AuthMethodPassword; +import pro.gravit.launcher.core.api.model.SelfUser; +import pro.gravit.launcher.core.api.model.Texture; +import pro.gravit.launcher.core.api.model.UserPermissions; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.core.backend.UserSettings; +import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException; +import pro.gravit.launcher.core.backend.extensions.Extension; +import pro.gravit.launcher.runtime.NewLauncherSettings; +import pro.gravit.launcher.runtime.managers.SettingsManager; +import pro.gravit.launcher.runtime.utils.LauncherUpdater; +import pro.gravit.utils.helper.JavaHelper; +import pro.gravit.utils.helper.LogHelper; + +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; + +public class LauncherBackendImpl implements LauncherBackendAPI { + private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this); + private volatile MainCallback callback; + ExecutorService executorService; + private volatile AuthMethod authMethod; + // Settings + private SettingsManager settingsManager; + private NewLauncherSettings allSettings; + private BackendSettings backendSettings; + // Data + private volatile List profiles; + private volatile UserPermissions permissions; + private volatile SelfUser selfUser; + private volatile List availableJavas; + private volatile CompletableFuture> availableJavasFuture; + + @Override + public void setCallback(MainCallback callback) { + this.callback = callback; + } + + private void doInit() throws Exception { + executorService = Executors.newScheduledThreadPool(2, (r) -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + }); + registerUserSettings("backend", BackendSettings.class); + settingsManager = new SettingsManager(); + settingsManager.generateConfigIfNotExists(); + settingsManager.loadConfig(); + allSettings = settingsManager.getConfig(); + backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings()); + permissions = new ClientPermissions(); + } + + @Override + public CompletableFuture init() { + try { + doInit(); + } catch (Throwable e) { + return CompletableFuture.failedFuture(e); + } + return LauncherAPIHolder.core().checkUpdates().thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> { + if(updatesInfo.required()) { + try { + LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL()); + } catch (Exception e) { + throw new RuntimeException(e); + } + callback.onShutdown(); + LauncherUpdater.restart(); + } + return new LauncherInitData(authMethods); + }, executorService); + } + + @Override + public void selectAuthMethod(AuthMethod method) { + this.authMethod = method; + LauncherAPIHolder.changeAuthId(method.getName()); + } + + @Override + public CompletableFuture tryAuthorize() { + if(this.authMethod == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod")); + } + if(backendSettings.auth == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("Auth data not found")); + } + if(backendSettings.auth.expireIn > 0 && LocalDateTime.ofInstant(Instant.ofEpochMilli(backendSettings.auth.expireIn), ZoneOffset.UTC).isBefore(LocalDateTime.now(ZoneOffset.UTC))) { + return LauncherAPIHolder.auth().refreshToken(backendSettings.auth.refreshToken).thenCompose((response) -> { + setAuthToken(response); + return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true); + }).thenApply((user) -> { + onAuthorize(user); + return user; + }); + } + return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true).thenApply((user) -> { + onAuthorize(user); + return user; + }); + } + + private void setAuthToken(AuthFeatureAPI.AuthToken authToken) { + backendSettings.auth = new BackendSettings.AuthorizationData(); + backendSettings.auth.accessToken = authToken.getAccessToken(); + backendSettings.auth.refreshToken = authToken.getRefreshToken(); + if(authToken.getExpire() <= 0) { + backendSettings.auth.expireIn = 0; + } + backendSettings.auth.expireIn = LocalDateTime.now(ZoneOffset.UTC) + .plus(authToken.getExpire(), ChronoUnit.MILLIS). + toEpochSecond(ZoneOffset.UTC); + } + + private void onAuthorize(SelfUser selfUser) { + permissions = selfUser.getPermissions(); + callback.onAuthorize(selfUser); + } + + @Override + public CompletableFuture authorize(String login, AuthMethodPassword password) { + if(this.authMethod == null) { + return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod")); + } + return LauncherAPIHolder.auth().auth(login, password).thenApply((response) -> { + setAuthToken(response.authToken()); + onAuthorize(response.user()); + return response.user(); + }); + } + + @Override + public CompletableFuture> fetchProfiles() { + return LauncherAPIHolder.profile().getProfiles().thenApply((profiles) -> { + this.profiles = profiles; + callback.onProfiles(profiles); + return profiles; + }); + } + + @Override + public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile) { + var settings = backendSettings.settings.get(profile.getUUID()); + if(settings == null) { + settings = new ProfileSettingsImpl((ClientProfile) profile); + } else { + settings = settings.copy(); + settings.initAfterGson((ClientProfile) profile, this); + } + return settings; + } + + @Override + public void saveClientProfileSettings(ClientProfileSettings settings) { + var impl = (ProfileSettingsImpl) settings; + impl.updateEnabledMods(); + backendSettings.settings.put(impl.profile.getUUID(), impl); + } + + @Override + public CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback) { + return clientDownloadImpl.downloadProfile((ClientProfile) profile, (ProfileSettingsImpl) settings, callback); + } + + @Override + public CompletableFuture fetchTexture(Texture texture) { + return null; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public CompletableFuture> getAvailableJava() { + if(availableJavas == null) { + if(availableJavasFuture == null) { + availableJavasFuture = CompletableFuture.supplyAsync(() -> { + return (List) JavaHelper.findJava(); // TODO: Custom Java + }, executorService).thenApply(e -> { + availableJavas = e; + return e; + }); + } + } + return CompletableFuture.completedFuture(availableJavas); + } + + @Override + public void registerUserSettings(String name, Class clazz) { + UserSettings.providers.register(name, clazz); + } + + @Override + public UserSettings getUserSettings(String name, Function ifNotExist) { + return allSettings.userSettings.computeIfAbsent(name, ifNotExist); + } + + @Override + public UserPermissions getPermissions() { + return permissions; + } + + @Override + public boolean hasPermission(String permission) { + return permissions.hasPerm(permission); + } + + @Override + public String getUsername() { + return selfUser == null ? "Player" : getUsername(); + } + + @Override + public SelfUser getSelfUser() { + return selfUser; + } + + @Override + public T getExtension(Class clazz) { + return null; + } + + @Override + public void shutdown() { + executorService.shutdownNow(); + try { + settingsManager.saveConfig(); + } catch (IOException e) { + LogHelper.error("Config not saved", e); + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java new file mode 100644 index 00000000..7e919d98 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -0,0 +1,173 @@ +package pro.gravit.launcher.runtime.backend; + +import oshi.SystemInfo; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.optional.OptionalFile; +import pro.gravit.launcher.base.profiles.optional.OptionalView; +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.runtime.utils.SystemMemory; +import pro.gravit.utils.helper.JVMHelper; +import pro.gravit.utils.helper.JavaHelper; + +import java.util.*; + +public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings { + transient ClientProfile profile; + transient LauncherBackendImpl backend; + @LauncherNetworkAPI + private Map ram; + @LauncherNetworkAPI + private Set flags; + @LauncherNetworkAPI + private Set enabled; + @LauncherNetworkAPI + private String saveJavaPath; + transient OptionalView view; + transient JavaHelper.JavaVersion selectedJava; + + public ProfileSettingsImpl() { + } + + public ProfileSettingsImpl(ClientProfile profile) { + this.profile = profile; + var def = profile.getSettings(); + this.ram = new HashMap<>(); + this.ram.put(MemoryClass.TOTAL, ((long)def.ram) << 20); + this.flags = new HashSet<>(); + if(def.autoEnter) { + this.flags.add(Flag.AUTO_ENTER); + } + if(def.fullScreen) { + this.flags.add(Flag.FULLSCREEN); + } + this.view = new OptionalView(profile); + } + + @Override + public long getReservedMemoryBytes(MemoryClass memoryClass) { + return ram.getOrDefault(memoryClass, 0L); + } + + @Override + public long getMaxMemoryBytes(MemoryClass memoryClass) { + try { + return SystemMemory.getPhysicalMemorySize(); + } catch (Throwable e) { + SystemInfo systemInfo = new SystemInfo(); + return systemInfo.getHardware().getMemory().getTotal(); + } + } + + @Override + public void setReservedMemoryBytes(MemoryClass memoryClass, long value) { + this.ram.put(memoryClass, value); + } + + @Override + public Set getFlags() { + return Collections.unmodifiableSet(flags); + } + + @Override + public Set getAvailableFlags() { + Set set = new HashSet<>(); + set.add(Flag.AUTO_ENTER); + set.add(Flag.FULLSCREEN); + if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { + set.add(Flag.LINUX_WAYLAND_SUPPORT); + } + if(backend.hasPermission("launcher.debug.skipfilemonitor")) { + set.add(Flag.DEBUG_SKIP_FILE_MONITOR); + } + return set; + } + + @Override + public boolean hasFlag(Flag flag) { + return flags.contains(flag); + } + + @Override + public void addFlag(Flag flag) { + flags.add(flag); + } + + @Override + public void removeFlag(Flag flag) { + flags.remove(flag); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public Set getEnabledOptionals() { + return (Set) view.enabled; + } + + @Override + public void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) { + view.enable((OptionalFile) mod, true, callback::onChanged); + } + + @Override + public void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) { + view.disable((OptionalFile) mod, callback::onChanged); + } + + @Override + public JavaHelper.JavaVersion getSelectedJava() { + return selectedJava; + } + + @Override + public void setSelectedJava(LauncherBackendAPI.Java java) { + selectedJava = (JavaHelper.JavaVersion) java; + } + + @Override + public boolean isRecommended(LauncherBackendAPI.Java java) { + return java.getMajorVersion() == profile.getRecommendJavaVersion(); + } + + @Override + public boolean isCompatible(LauncherBackendAPI.Java java) { + return java.getMajorVersion() >= profile.getMinJavaVersion() && java.getMajorVersion() <= profile.getMaxJavaVersion(); + } + + @Override + public ProfileSettingsImpl copy() { + ProfileSettingsImpl cloned = new ProfileSettingsImpl(); + cloned.profile = profile; + cloned.ram = new HashMap<>(ram); + cloned.flags = new HashSet<>(flags); + cloned.enabled = new HashSet<>(enabled); + if(view != null) { + cloned.view = new OptionalView(profile, view); + } + cloned.selectedJava = selectedJava; + cloned.saveJavaPath = saveJavaPath; + return cloned; + } + + public void updateEnabledMods() { + enabled = new HashSet<>(); + for(var e : view.enabled) { + enabled.add(e.name); + } + saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); + } + + public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { + this.backend = backend; + this.profile = profile; + this.view = new OptionalView(profile); + for(var e : enabled) { + var opt = profile.getOptionalFile(e); + if(opt == null) { + continue; + } + enableOptional(opt, (var1, var2) -> {}); + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java new file mode 100644 index 00000000..7649f40b --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java @@ -0,0 +1,128 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.profiles.ClientProfileBuilder; +import pro.gravit.launcher.base.profiles.PlayerProfile; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; +import pro.gravit.launcher.runtime.client.ClientLauncherProcess; +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JVMHelper; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.ArrayList; + +public class ReadyProfileImpl implements LauncherBackendAPI.ReadyProfile { + private LauncherBackendImpl backend; + private ClientProfile profile; + private ProfileSettingsImpl settings; + private ClientDownloadImpl.DownloadedDir clientDir; + private ClientDownloadImpl.DownloadedDir assetDir; + private ClientDownloadImpl.DownloadedDir javaDir; + private volatile Thread writeParamsThread; + private volatile Thread runThread; + private volatile ClientLauncherProcess process; + private volatile Process nativeProcess; + private volatile LauncherBackendAPI.RunCallback callback; + + public ReadyProfileImpl(LauncherBackendImpl backend, ClientProfile profile, ProfileSettingsImpl settings, ClientDownloadImpl.DownloadedDir clientDir, ClientDownloadImpl.DownloadedDir assetDir, ClientDownloadImpl.DownloadedDir javaDir) { + this.backend = backend; + this.profile = profile; + this.settings = settings; + this.clientDir = clientDir; + this.assetDir = assetDir; + this.javaDir = javaDir; + } + + @Override + public ProfileFeatureAPI.ClientProfile getClientProfile() { + return profile; + } + + @Override + public LauncherBackendAPI.ClientProfileSettings getSettings() { + return settings; + } + + @Override + public void run(LauncherBackendAPI.RunCallback callback) { + if(isAlive()) { + terminate(); + } + this.callback = callback; + if(backend.hasPermission("launcher.debug.skipfilemonitor") && settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.DEBUG_SKIP_FILE_MONITOR)) { + var builder = new ClientProfileBuilder(profile); + builder.setUpdate(new ArrayList<>()); + builder.setUpdateVerify(new ArrayList<>()); + builder.setUpdateExclusions(new ArrayList<>()); + profile = builder.createClientProfile(); + } + process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), settings.getSelectedJava(), clientDir.path().resolve("resourcepacks"), + profile, new PlayerProfile(backend.getSelfUser()), settings.view, backend.getSelfUser().getAccessToken(), + clientDir.dir(), assetDir.dir(), javaDir == null ? null : javaDir.dir()); + process.params.ram = (int) (settings.getReservedMemoryBytes(LauncherBackendAPI.ClientProfileSettings.MemoryClass.TOTAL) >> 20); + if (process.params.ram > 0) { + process.jvmArgs.add("-Xms" + process.params.ram + 'M'); + process.jvmArgs.add("-Xmx" + process.params.ram + 'M'); + } + process.params.fullScreen = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.FULLSCREEN); + process.params.autoEnter = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.AUTO_ENTER); + if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { + process.params.lwjglGlfwWayland = settings.hasFlag(LauncherBackendAPI.ClientProfileSettings.Flag.LINUX_WAYLAND_SUPPORT); + } + writeParamsThread = new Thread(this::writeParams); + writeParamsThread.setDaemon(true); + writeParamsThread.start(); + runThread = new Thread(this::readThread); + runThread.setDaemon(true); + runThread.start(); + } + + private void readThread() { + try { + process.start(true); + nativeProcess = process.getProcess(); + callback.onCanTerminate(this::terminate); + InputStream stream = nativeProcess.getInputStream(); + byte[] buf = IOHelper.newBuffer(); + try { + for (int length = stream.read(buf); length >= 0; length = stream.read(buf)) { + callback.onNormalOutput(buf, 0, length); + } + } catch (EOFException ignored) { + } + if (nativeProcess.isAlive()) { + int code = nativeProcess.waitFor(); + callback.onFinished(code); + } + } catch (Exception e) { + if(e instanceof InterruptedException) { + return; + } + terminate(); + } + } + + public void terminate() { + if(nativeProcess == null) { + return; + } + nativeProcess.destroyForcibly(); + } + + public boolean isAlive() { + return nativeProcess != null && nativeProcess.isAlive(); + } + + private void writeParams() { + try { + process.runWriteParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort)); + } catch (Throwable e) { + terminate(); + } + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java index 6f31c359..7e2f6130 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/RuntimeGsonManager.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.runtime.client; import com.google.gson.GsonBuilder; +import pro.gravit.launcher.core.backend.UserSettings; import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.core.managers.GsonManager; import pro.gravit.launcher.base.modules.events.PreGsonPhase; diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java new file mode 100644 index 00000000..bd4e21bc --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/AssetIndexHelper.java @@ -0,0 +1,65 @@ +package pro.gravit.launcher.runtime.utils; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.core.hasher.HashedDir; +import pro.gravit.launcher.core.hasher.HashedEntry; +import pro.gravit.utils.helper.IOHelper; + +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Path; +import java.util.*; + +public class AssetIndexHelper { + + public static AssetIndex parse(Path path) throws IOException { + try (Reader reader = IOHelper.newReader(path)) { + return Launcher.gsonManager.gson.fromJson(reader, AssetIndex.class); + } + } + + public static void modifyHashedDir(AssetIndex index, HashedDir original) { + Set hashes = new HashSet<>(); + for (AssetIndexObject obj : index.objects.values()) { + hashes.add(obj.hash); + } + HashedDir objects = (HashedDir) original.getEntry("objects"); + List toDeleteDirs = new ArrayList<>(16); + for (Map.Entry entry : objects.map().entrySet()) { + if (entry.getValue().getType() != HashedEntry.Type.DIR) { + continue; + } + HashedDir dir = (HashedDir) entry.getValue(); + List toDelete = new ArrayList<>(16); + for (String hash : dir.map().keySet()) { + if (!hashes.contains(hash)) { + toDelete.add(hash); + } + } + for (String s : toDelete) { + dir.remove(s); + } + if (dir.map().isEmpty()) { + toDeleteDirs.add(entry.getKey()); + } + } + for (String s : toDeleteDirs) { + objects.remove(s); + } + } + + public static class AssetIndex { + @LauncherNetworkAPI + public boolean virtual; + @LauncherNetworkAPI + public Map objects; + } + + public static class AssetIndexObject { + @LauncherNetworkAPI + public String hash; + @LauncherNetworkAPI + public long size; + } +} diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java new file mode 100644 index 00000000..89fb1a27 --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/utils/SystemMemory.java @@ -0,0 +1,10 @@ +package pro.gravit.launcher.runtime.utils; + +import com.sun.management.OperatingSystemMXBean; + +public class SystemMemory { + private static final OperatingSystemMXBean systemMXBean = (OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean(); + public static long getPhysicalMemorySize() { + return systemMXBean.getTotalMemorySize(); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java index 6eecdc21..f8f81f4d 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/Downloader.java @@ -235,7 +235,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do return task.get(); } - protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { + public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException { URI uri; if(baseUri != null) { String scheme = baseUri.getScheme(); @@ -248,6 +248,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy } else { uri = new URI(filePath); } + return uri; + } + + protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { + var uri = makeURI(baseUri, filePath); return HttpRequest.newBuilder() .GET() .uri(uri) diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java index 083bc4d4..dc778c39 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/PlayerProfile.java @@ -42,6 +42,14 @@ public PlayerProfile(UUID uuid, String username, Map assets, Ma this.properties = properties; } + @SuppressWarnings({"unchecked", "rawtypes"}) + public PlayerProfile(User user) { + this.uuid = user.getUUID(); + this.username = user.getUsername(); + this.assets = new HashMap<>((Map) user.getAssets()); + this.properties = user.getProperties(); + } + public static PlayerProfile newOfflineProfile(String username) { return new PlayerProfile(offlineUUID(username), username, new HashMap<>(), new HashMap<>()); } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java index b8a9d53c..6aaed9ae 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -165,5 +165,15 @@ public CompletableFuture fetchUpdateInfo(String dirName) { return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url)); } - public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {} + public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo { + @Override + public HashedDir getHashedDir() { + return hdir; + } + + @Override + public String getUrl() { + return url; + } + } } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java index a030d5da..8149911b 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -11,7 +11,10 @@ public interface ProfileFeatureAPI extends FeatureAPI { CompletableFuture> getProfiles(); CompletableFuture fetchUpdateInfo(String dirName); - interface UpdateInfo {} + interface UpdateInfo { + HashedDir getHashedDir(); + String getUrl(); + } interface ClientProfile { String getName(); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java index 36a01f49..7fa4a491 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -1,5 +1,6 @@ package pro.gravit.launcher.core.backend; +import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; import pro.gravit.launcher.core.api.method.AuthMethod; import pro.gravit.launcher.core.api.method.AuthMethodPassword; @@ -8,8 +9,11 @@ import pro.gravit.launcher.core.api.model.UserPermissions; import pro.gravit.launcher.core.backend.extensions.Extension; +import java.nio.file.Path; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; public interface LauncherBackendAPI { void setCallback(MainCallback callback); @@ -19,15 +23,22 @@ public interface LauncherBackendAPI { CompletableFuture authorize(String login, AuthMethodPassword password); CompletableFuture> fetchProfiles(); ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile); + void saveClientProfileSettings(ClientProfileSettings settings); CompletableFuture downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback); // Tools CompletableFuture fetchTexture(Texture texture); + CompletableFuture> getAvailableJava(); + // Settings + void registerUserSettings(String name, Class clazz); + UserSettings getUserSettings(String name, Function ifNotExist); // Status UserPermissions getPermissions(); boolean hasPermission(String permission); String getUsername(); + SelfUser getSelfUser(); // Extensions T getExtension(Class clazz); + void shutdown(); record LauncherInitData(List methods) {} @@ -38,20 +49,36 @@ interface ReadyProfile { } interface ClientProfileSettings { - long getReservedMemoryBytes(); - long getMaxMemoryBytes(); - void setReservedMemoryBytes(long value); - List getFlags(); + long getReservedMemoryBytes(MemoryClass memoryClass); + long getMaxMemoryBytes(MemoryClass memoryClass); + void setReservedMemoryBytes(MemoryClass memoryClass, long value); + Set getFlags(); + Set getAvailableFlags(); boolean hasFlag(Flag flag); void addFlag(Flag flag); void removeFlag(Flag flag); - List getEnabledOptionals(); + Set getEnabledOptionals(); void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback); - ClientProfileSettings clone(); + Java getSelectedJava(); + void setSelectedJava(Java java); + boolean isRecommended(Java java); + boolean isCompatible(Java java); + ClientProfileSettings copy(); enum Flag { + @LauncherNetworkAPI + AUTO_ENTER, + @LauncherNetworkAPI + FULLSCREEN, + @LauncherNetworkAPI + LINUX_WAYLAND_SUPPORT, + @LauncherNetworkAPI + DEBUG_SKIP_FILE_MONITOR + } + enum MemoryClass { + TOTAL } interface ChangedOptionalStatusCallback { @@ -78,6 +105,10 @@ public void onAuthorize(SelfUser selfUser) { public void onNotify(String header, String description) { } + + public void onShutdown() { + + } } class RunCallback { @@ -85,6 +116,10 @@ public void onStarted() { } + public void onCanTerminate(Runnable terminate) { + + } + public void onFinished(int code) { } @@ -99,6 +134,14 @@ public void onErrorOutput(byte[] buf, int offset, int size) { } class DownloadCallback { + public static final String STAGE_ASSET_VERIFY = "assetVerify"; + public static final String STAGE_HASHING = "hashing"; + public static final String STAGE_DIFF = "diff"; + public static final String STAGE_DOWNLOAD = "download"; + public static final String STAGE_DELETE_EXTRA = "deleteExtra"; + public static final String STAGE_DONE_PART = "done.part"; + public static final String STAGE_DONE = "done"; + public void onStage(String stage) { } @@ -115,4 +158,9 @@ public void onCurrentDownloaded(long current) { } } + + interface Java { + int getMajorVersion(); + Path getPath(); + } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java similarity index 77% rename from Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java rename to LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java index 37f4a105..2dafaace 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/UserSettings.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/UserSettings.java @@ -1,4 +1,4 @@ -package pro.gravit.launcher.runtime.client; +package pro.gravit.launcher.core.backend; import pro.gravit.utils.ProviderMap; diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java new file mode 100644 index 00000000..6890c40c --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/exceptions/LauncherBackendException.java @@ -0,0 +1,14 @@ +package pro.gravit.launcher.core.backend.exceptions; + +public class LauncherBackendException extends RuntimeException { + public LauncherBackendException() { + } + + public LauncherBackendException(String message) { + super(message); + } + + public LauncherBackendException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java b/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java index 77285591..a6d744d1 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/helper/JavaHelper.java @@ -1,5 +1,7 @@ package pro.gravit.utils.helper; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -186,7 +188,7 @@ public JavaVersionAndBuild() { } } - public static class JavaVersion { + public static class JavaVersion implements LauncherBackendAPI.Java { public final Path jvmDir; public final int version; public final int build; @@ -301,5 +303,15 @@ public static boolean isExistExtJavaLibrary(Path jvmDir, String name) { Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar")); return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin); } + + @Override + public int getMajorVersion() { + return version; + } + + @Override + public Path getPath() { + return jvmDir; + } } } diff --git a/build.gradle b/build.gradle index 211d7b5d..a658cec2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.1.0' apply false } group = 'pro.gravit.launcher' -version = '5.6.2' +version = '5.7.0-SNAPSHOT' apply from: 'props.gradle' From 83cc4415749f16c27fa63e670694b617b61eeb24 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:07:49 +0700 Subject: [PATCH 5/9] [FEATURE][EXPERIMENTAL] API holders --- .../launcher/runtime/LauncherEngine.java | 23 ++++++++++++++++--- .../backend/LauncherBackendAPIHolder.java | 13 +++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java index 8de18308..7574ab79 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/LauncherEngine.java @@ -2,7 +2,15 @@ import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.LauncherConfig; +import pro.gravit.launcher.base.request.*; import pro.gravit.launcher.client.*; +import pro.gravit.launcher.core.api.LauncherAPI; +import pro.gravit.launcher.core.api.LauncherAPIHolder; +import pro.gravit.launcher.core.api.features.AuthFeatureAPI; +import pro.gravit.launcher.core.api.features.ProfileFeatureAPI; +import pro.gravit.launcher.core.api.features.UserFeatureAPI; +import pro.gravit.launcher.core.backend.LauncherBackendAPIHolder; +import pro.gravit.launcher.runtime.backend.LauncherBackendImpl; import pro.gravit.launcher.runtime.client.*; import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase; import pro.gravit.launcher.client.events.ClientExitPhase; @@ -19,9 +27,6 @@ import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; -import pro.gravit.launcher.base.request.Request; -import pro.gravit.launcher.base.request.RequestException; -import pro.gravit.launcher.base.request.RequestService; import pro.gravit.launcher.base.request.auth.*; import pro.gravit.launcher.base.request.websockets.OfflineRequestService; import pro.gravit.launcher.base.request.websockets.StdWebSocketService; @@ -38,6 +43,7 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -244,6 +250,17 @@ public void start(String... args) throws Throwable { } Request.startAutoRefresh(); Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler()); + // Init New API + LauncherAPIHolder.setCoreAPI(new RequestCoreFeatureAPIImpl(Request.getRequestService())); + LauncherAPIHolder.setCreateApiFactory((authId) -> { + var impl = new RequestFeatureAPIImpl(Request.getRequestService(), authId); + return new LauncherAPI(Map.of( + AuthFeatureAPI.class, impl, + UserFeatureAPI.class, impl, + ProfileFeatureAPI.class, impl)); + }); + LauncherBackendAPIHolder.setApi(new LauncherBackendImpl()); + // Objects.requireNonNull(args, "args"); if (started.getAndSet(true)) throw new IllegalStateException("Launcher has been already started"); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java new file mode 100644 index 00000000..25b85a88 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPIHolder.java @@ -0,0 +1,13 @@ +package pro.gravit.launcher.core.backend; + +public class LauncherBackendAPIHolder { + private static volatile LauncherBackendAPI api; + + public static LauncherBackendAPI getApi() { + return api; + } + + public static void setApi(LauncherBackendAPI api) { + LauncherBackendAPIHolder.api = api; + } +} From d4d6491e522e97982721e3c163c5b4df136ad79f Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:09:46 +0700 Subject: [PATCH 6/9] [FIX][EXPERIMENTAL] API bug fixes --- .../runtime/backend/BackendSettings.java | 3 +- .../runtime/backend/LauncherBackendImpl.java | 57 ++++++++++++++++--- .../launcher/runtime/client/ServerPinger.java | 19 ++++++- .../launcher/base/profiles/ClientProfile.java | 17 +++++- .../base/request/RequestFeatureAPIImpl.java | 6 +- .../core/api/features/ProfileFeatureAPI.java | 6 ++ .../core/backend/LauncherBackendAPI.java | 8 +++ 7 files changed, 105 insertions(+), 11 deletions(-) diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java index 1d64ee03..0ec6872e 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java @@ -3,6 +3,7 @@ import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.backend.UserSettings; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -10,7 +11,7 @@ public class BackendSettings extends UserSettings { @LauncherNetworkAPI public AuthorizationData auth; @LauncherNetworkAPI - public Map settings; + public Map settings = new HashMap<>(); public static class AuthorizationData { @LauncherNetworkAPI public String accessToken; diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java index cfd85d0b..6b9557d1 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java @@ -4,6 +4,7 @@ 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; @@ -15,6 +16,8 @@ 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.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; @@ -27,7 +30,10 @@ 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; @@ -47,6 +53,7 @@ public class LauncherBackendImpl implements LauncherBackendAPI { private volatile SelfUser selfUser; private volatile List availableJavas; private volatile CompletableFuture> availableJavasFuture; + private final Map> pingFutures = new ConcurrentHashMap<>(); @Override public void setCallback(MainCallback callback) { @@ -75,7 +82,13 @@ public CompletableFuture init() { } catch (Throwable e) { return CompletableFuture.failedFuture(e); } - return LauncherAPIHolder.core().checkUpdates().thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> { + CompletableFuture feature; + if(isTestMode()) { + feature = CompletableFuture.completedFuture(new CoreFeatureAPI.LauncherUpdateInfo(null, "Unknown", false, false)); + } else { + feature = LauncherAPIHolder.core().checkUpdates(); + } + return feature.thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> { if(updatesInfo.required()) { try { LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL()); @@ -131,6 +144,7 @@ private void setAuthToken(AuthFeatureAPI.AuthToken authToken) { } private void onAuthorize(SelfUser selfUser) { + this.selfUser = selfUser; permissions = selfUser.getPermissions(); callback.onAuthorize(selfUser); } @@ -182,7 +196,7 @@ public CompletableFuture downloadProfile(ProfileFeatureAPI.ClientP @Override public CompletableFuture fetchTexture(Texture texture) { - return null; + return CompletableFuture.failedFuture(new UnsupportedOperationException()); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -201,6 +215,22 @@ public CompletableFuture> getAvailableJava() { return CompletableFuture.completedFuture(availableJavas); } + @Override + public CompletableFuture pingServer(ProfileFeatureAPI.ClientProfile profile) { + return pingFutures.computeIfAbsent(profile.getUUID(), (k) -> { + CompletableFuture future = new CompletableFuture<>(); + executorService.submit(() -> { + try { + ServerPinger pinger = new ServerPinger((ClientProfile) profile); + future.complete(pinger.ping()); + } catch (Throwable e) { + future.completeExceptionally(e); + } + }); + return future; + }); + } + @Override public void registerUserSettings(String name, Class clazz) { UserSettings.providers.register(name, clazz); @@ -231,6 +261,15 @@ public SelfUser getSelfUser() { return selfUser; } + @Override + public boolean isTestMode() { + try { + return DebugMain.IS_DEBUG.get(); + } catch (Throwable ex) { + return false; + } + } + @Override public T getExtension(Class clazz) { return null; @@ -238,11 +277,15 @@ public T getExtension(Class clazz) { @Override public void shutdown() { - executorService.shutdownNow(); - try { - settingsManager.saveConfig(); - } catch (IOException e) { - LogHelper.error("Config not saved", e); + if(executorService != null) { + executorService.shutdownNow(); + } + if(settingsManager != null) { + try { + settingsManager.saveConfig(); + } catch (IOException e) { + LogHelper.error("Config not saved", e); + } } } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java index 53f113b2..7f703e30 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ServerPinger.java @@ -5,6 +5,7 @@ import com.google.gson.JsonParser; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfileVersions; +import pro.gravit.launcher.core.backend.LauncherBackendAPI; import pro.gravit.launcher.core.serialize.HInput; import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.utils.helper.IOHelper; @@ -18,6 +19,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; +import java.util.List; import java.util.Objects; import java.util.regex.Pattern; @@ -208,7 +210,7 @@ public Result ping() throws IOException { } } - public static final class Result { + public static final class Result implements LauncherBackendAPI.ServerPingInfo { public final int onlinePlayers; @@ -228,5 +230,20 @@ public Result(int onlinePlayers, int maxPlayers, String raw) { public boolean isOverfilled() { return onlinePlayers >= maxPlayers; } + + @Override + public int getMaxOnline() { + return maxPlayers; + } + + @Override + public int getOnline() { + return onlinePlayers; + } + + @Override + public List getPlayerNames() { + return List.of(); + } } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java index 137cd961..829f8651 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/ClientProfile.java @@ -380,6 +380,11 @@ public Map getProperties() { return Collections.unmodifiableMap(properties); } + @Override + public ServerInfo getServer() { + return getDefaultServerProfile(); + } + public List getCompatClasses() { return Collections.unmodifiableList(compatClasses); } @@ -525,7 +530,7 @@ public JsonElement serialize(Version src, Type typeOfSrc, JsonSerializationConte } } - public static class ServerProfile { + public static class ServerProfile implements ServerInfo { public String name; public String serverAddress; public int serverPort; @@ -552,6 +557,16 @@ public ServerProfile(String name, String serverAddress, int serverPort, boolean public InetSocketAddress toSocketAddress() { return InetSocketAddress.createUnresolved(serverAddress, serverPort); } + + @Override + public String getAddress() { + return serverAddress; + } + + @Override + public int getPort() { + return serverPort; + } } public static class ProfileDefaultSettings { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java index 6aaed9ae..f828e36f 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -42,7 +42,11 @@ public CompletableFuture getCurrentUser() { @Override public CompletableFuture auth(String login, AuthMethodPassword password) { - return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, AuthRequest.ConnectTypes.CLIENT)) + 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)); } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java index 8149911b..6d5abbfa 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -23,6 +23,12 @@ interface ClientProfile { List getOptionalMods(); String getProperty(String name); Map getProperties(); + ServerInfo getServer(); + + interface ServerInfo { + String getAddress(); + int getPort(); + } } interface OptionalMod { diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java index 7fa4a491..4851f3a0 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -28,6 +28,7 @@ public interface LauncherBackendAPI { // Tools CompletableFuture fetchTexture(Texture texture); CompletableFuture> getAvailableJava(); + CompletableFuture pingServer(ProfileFeatureAPI.ClientProfile profile); // Settings void registerUserSettings(String name, Class clazz); UserSettings getUserSettings(String name, Function ifNotExist); @@ -36,6 +37,7 @@ public interface LauncherBackendAPI { boolean hasPermission(String permission); String getUsername(); SelfUser getSelfUser(); + boolean isTestMode(); // Extensions T getExtension(Class clazz); void shutdown(); @@ -163,4 +165,10 @@ interface Java { int getMajorVersion(); Path getPath(); } + + interface ServerPingInfo { + int getMaxOnline(); + int getOnline(); + List getPlayerNames(); + } } From f1559183a121593f79803651eeae31138b9b6320 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:33:53 +0700 Subject: [PATCH 7/9] [FIX} New API bug fixes --- .../runtime/backend/BackendSettings.java | 20 +++++++++++++ .../runtime/backend/ClientDownloadImpl.java | 14 ++++++--- .../runtime/backend/LauncherBackendImpl.java | 15 +++++++++- .../runtime/backend/ProfileSettingsImpl.java | 30 ++++++++++++++++++- .../runtime/backend/ReadyProfileImpl.java | 11 +++++-- .../runtime/client/ClientLauncherProcess.java | 13 ++++++-- .../base/events/request/AuthRequestEvent.java | 6 ++++ .../base/request/RequestFeatureAPIImpl.java | 8 +++++ .../core/api/features/ProfileFeatureAPI.java | 1 + .../core/backend/LauncherBackendAPI.java | 1 + build.gradle | 2 +- 11 files changed, 109 insertions(+), 12 deletions(-) diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java index 0ec6872e..c42a2837 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/BackendSettings.java @@ -1,6 +1,7 @@ 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; @@ -19,5 +20,24 @@ public static class AuthorizationData { public String refreshToken; @LauncherNetworkAPI public long expireIn; + + public AuthFeatureAPI.AuthToken toToken() { + return new AuthFeatureAPI.AuthToken() { + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public String getRefreshToken() { + return refreshToken; + } + + @Override + public long getExpire() { + return expireIn; + } + }; + } } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java index 70601963..c9eb88ea 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ClientDownloadImpl.java @@ -37,7 +37,8 @@ CompletableFuture downloadProfile(ClientProfile AtomicReference clientRef = new AtomicReference<>(); AtomicReference assetRef = new AtomicReference<>(); AtomicReference javaRef = new AtomicReference<>(); - return downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback).thenCompose((clientDir -> { + 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 -> { @@ -53,7 +54,7 @@ CompletableFuture downloadProfile(ClientProfile CompletableFuture downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) { Path targetDir = DirBridge.dirUpdates.resolve(dirName); - Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString); + 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); @@ -116,7 +117,7 @@ CompletableFuture downloadDir(Path targetDir, ProfileFeatureAPI.U callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING); HashedDir realFiles = new HashedDir(targetDir, matcher, false, true); callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF); - return realFiles.diff(updateInfo.getHashedDir(), matcher); + return updateInfo.getHashedDir().diff(realFiles, matcher); } catch (IOException e) { throw new RuntimeException(e); } @@ -165,7 +166,12 @@ private List collectFilesAndCreateDirectories(Path dir, Ha List files = new ArrayList<>(); mismatch.walk(File.separator, (path, name, entry) -> { if(entry.getType() == HashedEntry.Type.DIR) { - Files.createDirectory(dir.resolve(path)); + var dirPath = dir.resolve(path); + if(!Files.exists(dirPath)) { + Files.createDirectory(dirPath); + } else if (!Files.isDirectory(dirPath)) { + throw new IOException(String.format("%s is not a directory", path)); + } return HashedDir.WalkAction.CONTINUE; } String pathFixed = path.replace(File.separatorChar, '/'); diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java index 6b9557d1..a8191c9b 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/LauncherBackendImpl.java @@ -16,6 +16,7 @@ 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; @@ -73,6 +74,7 @@ private void doInit() throws Exception { allSettings = settingsManager.getConfig(); backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings()); permissions = new ClientPermissions(); + DirBridge.dirUpdates = DirBridge.defaultUpdatesDir; } @Override @@ -102,6 +104,14 @@ public CompletableFuture init() { }, executorService); } + public AuthFeatureAPI.AuthToken getAuthToken() { + return backendSettings.auth.toToken(); + } + + public AuthMethod getAuthMethod() { + return authMethod; + } + @Override public void selectAuthMethod(AuthMethod method) { this.authMethod = method; @@ -175,9 +185,11 @@ public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientP 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); + //settings.initAfterGson((ClientProfile) profile, this); } return settings; } @@ -211,6 +223,7 @@ public CompletableFuture> getAvailableJava() { return e; }); } + return availableJavasFuture; } return CompletableFuture.completedFuture(availableJavas); } diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java index 7e919d98..cd175ec4 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -12,6 +12,7 @@ import pro.gravit.utils.helper.JavaHelper; import java.util.*; +import java.util.concurrent.ExecutionException; public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings { transient ClientProfile profile; @@ -120,6 +121,30 @@ 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; @@ -138,6 +163,7 @@ public boolean isCompatible(LauncherBackendAPI.Java java) { @Override public ProfileSettingsImpl copy() { ProfileSettingsImpl cloned = new ProfileSettingsImpl(); + cloned.backend = backend; cloned.profile = profile; cloned.ram = new HashMap<>(ram); cloned.flags = new HashSet<>(flags); @@ -155,7 +181,9 @@ public void updateEnabledMods() { for(var e : view.enabled) { enabled.add(e.name); } - saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); + if(selectedJava != null) { + saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); + } } public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java index 7649f40b..c3092161 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/ReadyProfileImpl.java @@ -1,9 +1,11 @@ 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; @@ -61,9 +63,14 @@ public void run(LauncherBackendAPI.RunCallback callback) { builder.setUpdateExclusions(new ArrayList<>()); profile = builder.createClientProfile(); } - process = new ClientLauncherProcess(clientDir.path(), assetDir.path(), settings.getSelectedJava(), clientDir.path().resolve("resourcepacks"), + 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()); + 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'); diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java index dc1b5bcd..ea3e12a4 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java @@ -2,6 +2,7 @@ import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.LauncherConfig; +import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.client.ClientLauncherEntryPoint; import pro.gravit.launcher.client.ClientParams; import pro.gravit.launcher.runtime.LauncherEngine; @@ -70,6 +71,12 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir, ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) { + this(clientDir, assetDir, javaVersion, resourcePackDir, profile, playerProfile, view, accessToken, clientHDir, assetHDir, jvmHDir, null, null); + } + + public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir, + ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, + HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir, AuthRequestEvent.OAuthRequestEvent oAuthRequestEvent, String authId) { this.javaVersion = javaVersion; this.workDir = clientDir.toAbsolutePath(); this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir); @@ -78,6 +85,8 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString(); this.params.timestamp = System.currentTimeMillis(); + this.params.oauth = oAuthRequestEvent; + this.params.authId = authId; Path nativesPath; if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) { nativesPath = workDir.resolve("natives"); @@ -120,10 +129,8 @@ private void applyClientProfile() { if (params.ram > 0) { this.jvmArgs.add("-Xmx" + params.ram + 'M'); } - this.params.oauth = Request.getOAuth(); if (this.params.oauth == null) { - throw new UnsupportedOperationException("Legacy session not supported"); - } else { + this.params.oauth = Request.getOAuth(); this.params.authId = Request.getAuthId(); this.params.oauthExpiredTime = Request.getTokenExpiredTime(); this.params.extendedTokens = Request.getExtendedTokens(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java index 7f6c5f61..a7e0584e 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/events/request/AuthRequestEvent.java @@ -87,6 +87,12 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long 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; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java index f828e36f..39994d24 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/request/RequestFeatureAPIImpl.java @@ -1,6 +1,7 @@ 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; @@ -89,6 +90,8 @@ private AuthRequest.AuthPasswordInterface convertAuthPassword(AuthMethodPassword return new AuthCodePassword(oauth.redirectUrl()); } else if(password instanceof AuthRequest.AuthPasswordInterface custom) { return custom; + } else if(password == null) { + return null; } else { throw new UnsupportedOperationException(); @@ -164,6 +167,11 @@ public CompletableFuture> getProfiles() { return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles); } + @Override + public CompletableFuture changeCurrentProfile(ClientProfile profile) { + return request.request(new SetProfileRequest((pro.gravit.launcher.base.profiles.ClientProfile) profile)).thenApply(response -> null); + } + @Override public CompletableFuture fetchUpdateInfo(String dirName) { return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url)); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java index 6d5abbfa..afd541bf 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -9,6 +9,7 @@ public interface ProfileFeatureAPI extends FeatureAPI { CompletableFuture> getProfiles(); + CompletableFuture changeCurrentProfile(ClientProfile profile); CompletableFuture fetchUpdateInfo(String dirName); interface UpdateInfo { diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java index 4851f3a0..e367f11b 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/backend/LauncherBackendAPI.java @@ -63,6 +63,7 @@ interface ClientProfileSettings { 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); diff --git a/build.gradle b/build.gradle index 1a1f5a77..a658cec2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.1.0' apply false } group = 'pro.gravit.launcher' -version = '5.6.8' +version = '5.7.0-SNAPSHOT' apply from: 'props.gradle' From f946c893e113316a3dffbc1bc1ed29a66683b8ab Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:30:14 +0700 Subject: [PATCH 8/9] [FEATURE] Add OptionalMod.getDependencies() --- .../launcher/base/profiles/optional/OptionalFile.java | 6 ++++++ .../launcher/core/api/features/ProfileFeatureAPI.java | 2 ++ 2 files changed, 8 insertions(+) diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java index bf8e261c..d16a13a8 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Objects; +import java.util.Set; public class OptionalFile implements ProfileFeatureAPI.OptionalMod { @LauncherNetworkAPI @@ -72,6 +73,11 @@ public boolean isVisible() { return visible; } + @Override + public Set getDependencies() { + return Set.of(dependencies); + } + public boolean isMark() { return mark; } diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java index afd541bf..ddf7d032 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/core/api/features/ProfileFeatureAPI.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -37,5 +38,6 @@ interface OptionalMod { String getDescription(); String getCategory(); boolean isVisible(); + Set getDependencies(); } } From 27bcfc046ef776bd6dff452c70f00307d494931f Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:54:23 +0700 Subject: [PATCH 9/9] [FEATURE] Upgrade minimum version to Java 21 --- .../pro/gravit/launchserver/helper/MakeProfileHelper.java | 8 ++------ Launcher/build.gradle | 6 +++--- LauncherAPI/build.gradle | 4 ++-- LauncherClient/build.gradle | 4 ++-- LauncherCore/build.gradle | 4 ++-- LauncherStart/build.gradle | 4 ++-- ServerWrapper/build.gradle | 6 +++--- build.gradle | 2 +- modules | 2 +- 9 files changed, 18 insertions(+), 22 deletions(-) diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java index 5b375301..c1c51b1c 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/MakeProfileHelper.java @@ -153,12 +153,8 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti } } } - builder.setMinJavaVersion(17); - builder.setRecommendJavaVersion(17); - if(version.compareTo(ClientProfileVersions.MINECRAFT_1_20_3) >= 0) { - builder.setMinJavaVersion(21); - builder.setRecommendJavaVersion(21); - } + builder.setMinJavaVersion(21); + builder.setRecommendJavaVersion(21); jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true"); jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); builder.setJvmArgs(jvmArgs); diff --git a/Launcher/build.gradle b/Launcher/build.gradle index 42ed13a7..2852258a 100644 --- a/Launcher/build.gradle +++ b/Launcher/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'com.gradleup.shadow' String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper" @@ -7,8 +7,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' configurations { bundle diff --git a/LauncherAPI/build.gradle b/LauncherAPI/build.gradle index d66ee2a7..76b3a3eb 100644 --- a/LauncherAPI/build.gradle +++ b/LauncherAPI/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' dependencies { api project(':LauncherCore') diff --git a/LauncherClient/build.gradle b/LauncherClient/build.gradle index 78ad4310..8562cfe8 100644 --- a/LauncherClient/build.gradle +++ b/LauncherClient/build.gradle @@ -7,8 +7,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/LauncherCore/build.gradle b/LauncherCore/build.gradle index 068bbf70..131a0dc9 100644 --- a/LauncherCore/build.gradle +++ b/LauncherCore/build.gradle @@ -1,5 +1,5 @@ -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' dependencies { compileOnly group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] diff --git a/LauncherStart/build.gradle b/LauncherStart/build.gradle index 225658f7..94b2c502 100644 --- a/LauncherStart/build.gradle +++ b/LauncherStart/build.gradle @@ -8,8 +8,8 @@ url "https://repo.spring.io/plugins-release/" } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/ServerWrapper/build.gradle b/ServerWrapper/build.gradle index 68a4f2bc..ea46bbc7 100644 --- a/ServerWrapper/build.gradle +++ b/ServerWrapper/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'com.gradleup.shadow' String mainClassName = "pro.gravit.launcher.server.ServerWrapper" String mainAgentName = "pro.gravit.launcher.server.ServerAgent" @@ -14,8 +14,8 @@ } } -sourceCompatibility = '17' -targetCompatibility = '17' +sourceCompatibility = '21' +targetCompatibility = '21' jar { archiveClassifier.set('clean') diff --git a/build.gradle b/build.gradle index a658cec2..ca3ac1f0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.github.johnrengelman.shadow' version '7.1.2' apply false + id 'com.gradleup.shadow' version '8.3.5' apply false id 'maven-publish' id 'signing' id 'org.openjfx.javafxplugin' version '0.1.0' apply false diff --git a/modules b/modules index 0d8cef92..755009c2 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 0d8cef927b1fda3097dc88c3adcffc4d0e33dd69 +Subproject commit 755009c292ce35273b8a7e584088a0932ab17e7c