Compare commits

...

7 commits

Author SHA1 Message Date
Antoni
900c028653
Merge 0e1691ee4c into 15ed66b3d5 2025-06-14 10:40:03 +03:00
Gravita
15ed66b3d5 [FIX] Implement getMinecraftVersion() 2025-06-13 17:30:28 +07:00
Gravita
cacbbfc8ed [FIX] initAfterGson selectedJava and fix texture upload 2025-06-13 17:11:18 +07:00
Gravita
0a163bb09c [FIX] Create updates directory if not exist 2025-06-13 16:07:47 +07:00
Gravita
f246ab697b [FEATURE] Implement HardwareVerificationFeatureAPI 2025-06-13 15:59:58 +07:00
Gravita
a03de56dde [FEATURE] Implement TextureUploadExtension 2025-06-13 14:21:04 +07:00
microwin7
0e1691ee4c
Add Discord Widget 2023-05-19 22:40:26 +03:00
19 changed files with 424 additions and 130 deletions

View file

@ -6,18 +6,14 @@
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.api.features.*;
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;
import pro.gravit.launcher.runtime.client.events.ClientPreGuiPhase;
import pro.gravit.launcher.runtime.console.GetPublicKeyCommand;
import pro.gravit.launcher.runtime.console.ModulesCommand;
import pro.gravit.launcher.runtime.console.SignDataCommand;
import pro.gravit.launcher.runtime.gui.NoRuntimeProvider;
import pro.gravit.launcher.runtime.gui.RuntimeProvider;
import pro.gravit.launcher.runtime.managers.ConsoleManager;
@ -33,15 +29,8 @@
import pro.gravit.launcher.start.RuntimeModuleManager;
import pro.gravit.utils.helper.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@ -54,8 +43,6 @@ public class LauncherEngine {
// Instance
private final AtomicBoolean started = new AtomicBoolean(false);
public RuntimeProvider runtimeProvider;
public ECPublicKey publicKey;
public ECPrivateKey privateKey;
public Class<? extends RuntimeProvider> basicRuntimeProvider;
private LauncherEngine(boolean clientInstance, Class<? extends RuntimeProvider> basicRuntimeProvider) {
@ -182,36 +169,6 @@ public static LauncherEngine newInstance(boolean clientInstance, Class<? extends
return new LauncherEngine(clientInstance, basicRuntimeProvider);
}
public ECPublicKey getClientPublicKey() {
return publicKey;
}
public byte[] sign(byte[] bytes) {
return SecurityHelper.sign(bytes, privateKey);
}
public void readKeys() throws IOException, InvalidKeySpecException {
if (privateKey != null || publicKey != null) return;
Path dir = DirBridge.dir;
Path publicKeyFile = dir.resolve("public.key");
Path privateKeyFile = dir.resolve("private.key");
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
LogHelper.info("Reading EC keypair");
publicKey = SecurityHelper.toPublicECDSAKey(IOHelper.read(publicKeyFile));
privateKey = SecurityHelper.toPrivateECDSAKey(IOHelper.read(privateKeyFile));
} else {
LogHelper.info("Generating EC keypair");
KeyPair pair = SecurityHelper.genECDSAKeyPair(new SecureRandom());
publicKey = (ECPublicKey) pair.getPublic();
privateKey = (ECPrivateKey) pair.getPrivate();
// Write key pair list
LogHelper.info("Writing EC keypair list");
IOHelper.write(publicKeyFile, publicKey.getEncoded());
IOHelper.write(privateKeyFile, privateKey.getEncoded());
}
}
public void start(String... args) throws Throwable {
//Launcher.modulesManager = new ClientModuleManager(this);
ClientPreGuiPhase event = new ClientPreGuiPhase(null);
@ -256,14 +213,15 @@ public void start(String... args) throws Throwable {
return new LauncherAPI(Map.of(
AuthFeatureAPI.class, impl,
UserFeatureAPI.class, impl,
ProfileFeatureAPI.class, impl));
ProfileFeatureAPI.class, impl,
TextureUploadFeatureAPI.class, impl,
HardwareVerificationFeatureAPI.class, impl));
});
LauncherBackendAPIHolder.setApi(new LauncherBackendImpl());
//
Objects.requireNonNull(args, "args");
if (started.getAndSet(true))
throw new IllegalStateException("Launcher has been already started");
readKeys();
registerCommands();
LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this));
runtimeProvider.preLoad();
@ -272,8 +230,6 @@ public void start(String... args) throws Throwable {
}
private void registerCommands() {
ConsoleManager.handler.registerCommand("getpublickey", new GetPublicKeyCommand(this));
ConsoleManager.handler.registerCommand("signdata", new SignDataCommand(this));
ConsoleManager.handler.registerCommand("modules", new ModulesCommand());
}
}

View file

@ -115,6 +115,9 @@ CompletableFuture<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.U
return CompletableFuture.supplyAsync(() -> {
try {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING);
if(!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
HashedDir realFiles = new HashedDir(targetDir, matcher, false, true);
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF);
return updateInfo.getHashedDir().diff(realFiles, matcher);

View file

@ -0,0 +1,49 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
public class ECKeyHolder {
public ECPublicKey publicKey;
public ECPrivateKey privateKey;
public ECPublicKey getClientPublicKey() {
return publicKey;
}
public byte[] sign(byte[] bytes) {
return SecurityHelper.sign(bytes, privateKey);
}
public void readKeys() throws IOException, InvalidKeySpecException {
if (privateKey != null || publicKey != null) return;
Path dir = DirBridge.dir;
Path publicKeyFile = dir.resolve("public.key");
Path privateKeyFile = dir.resolve("private.key");
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
LogHelper.info("Reading EC keypair");
publicKey = SecurityHelper.toPublicECDSAKey(IOHelper.read(publicKeyFile));
privateKey = SecurityHelper.toPrivateECDSAKey(IOHelper.read(privateKeyFile));
} else {
LogHelper.info("Generating EC keypair");
KeyPair pair = SecurityHelper.genECDSAKeyPair(new SecureRandom());
publicKey = (ECPublicKey) pair.getPublic();
privateKey = (ECPrivateKey) pair.getPrivate();
// Write key pair list
LogHelper.info("Writing EC keypair list");
IOHelper.write(publicKeyFile, publicKey.getEncoded());
IOHelper.write(privateKeyFile, privateKey.getEncoded());
}
}
}

View file

@ -3,9 +3,7 @@
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.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;
@ -15,14 +13,18 @@
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.core.backend.extensions.TextureUploadExtension;
import pro.gravit.launcher.runtime.LauncherEngine;
import pro.gravit.launcher.runtime.NewLauncherSettings;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.client.ServerPinger;
import pro.gravit.launcher.runtime.debug.DebugMain;
import pro.gravit.launcher.runtime.managers.SettingsManager;
import pro.gravit.launcher.runtime.utils.HWIDProvider;
import pro.gravit.launcher.runtime.utils.LauncherUpdater;
import pro.gravit.utils.helper.JavaHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.net.URI;
@ -39,7 +41,7 @@
import java.util.concurrent.Executors;
import java.util.function.Function;
public class LauncherBackendImpl implements LauncherBackendAPI {
public class LauncherBackendImpl implements LauncherBackendAPI, TextureUploadExtension {
private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this);
private volatile MainCallback callback;
ExecutorService executorService;
@ -48,12 +50,15 @@ public class LauncherBackendImpl implements LauncherBackendAPI {
private SettingsManager settingsManager;
private NewLauncherSettings allSettings;
private BackendSettings backendSettings;
// Hardware
private volatile ECKeyHolder ecKeyHolder;
// Data
private volatile List<ProfileFeatureAPI.ClientProfile> profiles;
private volatile UserPermissions permissions;
private volatile SelfUser selfUser;
private volatile List<Java> availableJavas;
private volatile CompletableFuture<List<Java>> availableJavasFuture;
private volatile CompletableFuture<Void> processHardwareFuture;
private final Map<UUID, CompletableFuture<ServerPingInfo>> pingFutures = new ConcurrentHashMap<>();
@Override
@ -74,6 +79,8 @@ private void doInit() throws Exception {
allSettings = settingsManager.getConfig();
backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings());
permissions = new ClientPermissions();
ecKeyHolder = new ECKeyHolder();
ecKeyHolder.readKeys();
DirBridge.dirUpdates = DirBridge.defaultUpdatesDir;
}
@ -157,6 +164,9 @@ private void onAuthorize(SelfUser selfUser) {
this.selfUser = selfUser;
permissions = selfUser.getPermissions();
callback.onAuthorize(selfUser);
if(processHardwareFuture == null) {
processHardwareFuture = processHardware();
}
}
@Override
@ -285,8 +295,14 @@ public boolean isTestMode() {
}
}
@SuppressWarnings("unchecked")
@Override
public <T extends Extension> T getExtension(Class<T> clazz) {
if(clazz == TextureUploadExtension.class) {
if(authMethod != null && authMethod.getFeatures().contains(TextureUploadFeatureAPI.FEATURE_NAME)) {
return (T) this;
}
}
return null;
}
@ -303,4 +319,43 @@ public void shutdown() {
}
}
}
@Override
public CompletableFuture<TextureUploadFeatureAPI.TextureUploadInfo> fetchTextureUploadInfo() {
return LauncherAPIHolder.get().get(TextureUploadFeatureAPI.class).fetchInfo();
}
@Override
public CompletableFuture<Texture> uploadTexture(String name, byte[] bytes, TextureUploadFeatureAPI.UploadSettings settings) {
return LauncherAPIHolder.get().get(TextureUploadFeatureAPI.class).upload(name, bytes, settings);
}
public CompletableFuture<Void> processHardware() {
HardwareVerificationFeatureAPI featureAPI = LauncherAPIHolder.get().get(HardwareVerificationFeatureAPI.class);
if(featureAPI == null) {
return CompletableFuture.completedFuture(null);
}
return featureAPI.getSecurityInfo().thenCompose((response) -> {
if(!response.isRequired()) {
return CompletableFuture.completedFuture(null);
}
byte[] signature = SecurityHelper.sign(response.getSignData(), ecKeyHolder.privateKey);
return featureAPI.privateKeyVerification(ecKeyHolder.publicKey, signature);
}).thenCompose((response) -> {
switch (response.getHardwareCollectLevel()) {
case NONE -> {
return featureAPI.sendHardwareInfo(null, null);
}
case ONLY_STATISTIC -> {
HWIDProvider hwidProvider = new HWIDProvider();
return featureAPI.sendHardwareInfo(hwidProvider.getStatisticData(), null);
}
case ALL -> {
HWIDProvider hwidProvider = new HWIDProvider();
return featureAPI.sendHardwareInfo(hwidProvider.getStatisticData(), hwidProvider.getIdentifyData());
}
}
return CompletableFuture.failedFuture(new UnsupportedOperationException());
});
}
}

View file

@ -26,7 +26,7 @@ public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSett
@LauncherNetworkAPI
private String saveJavaPath;
transient OptionalView view;
transient JavaHelper.JavaVersion selectedJava;
transient volatile JavaHelper.JavaVersion selectedJava;
public ProfileSettingsImpl() {
}
@ -203,5 +203,21 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {
}
enableOptional(opt, (var1, var2) -> {});
}
if(this.saveJavaPath != null) {
backend.getAvailableJava().thenAccept((javas) -> {
for(var java : javas) {
if(!isCompatible(java)) {
continue;
}
if(java.getPath() == null) {
continue;
}
if(java.getPath().toAbsolutePath().toString().equals(this.saveJavaPath)) {
this.selectedJava = (JavaHelper.JavaVersion) java;
return;
}
}
});
}
}
}

View file

@ -1,30 +0,0 @@
package pro.gravit.launcher.runtime.console;
import pro.gravit.launcher.runtime.LauncherEngine;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
import java.util.Base64;
public class GetPublicKeyCommand extends Command {
private final LauncherEngine engine;
public GetPublicKeyCommand(LauncherEngine engine) {
this.engine = engine;
}
@Override
public String getArgsDescription() {
return "[]";
}
@Override
public String getUsageDescription() {
return "print public key in base64 format";
}
@Override
public void invoke(String... args) {
LogHelper.info("PublicKey: %s", Base64.getEncoder().encodeToString(engine.getClientPublicKey().getEncoded()));
}
}

View file

@ -1,34 +0,0 @@
package pro.gravit.launcher.runtime.console;
import pro.gravit.launcher.runtime.LauncherEngine;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
import java.util.Base64;
public class SignDataCommand extends Command {
private final LauncherEngine engine;
public SignDataCommand(LauncherEngine engine) {
this.engine = engine;
}
@Override
public String getArgsDescription() {
return "[base64 data]";
}
@Override
public String getUsageDescription() {
return "sign any data";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
byte[] data = Base64.getDecoder().decode(args[0]);
byte[] signature = engine.sign(data);
String base64 = Base64.getEncoder().encodeToString(signature);
LogHelper.info("Signature: %s", base64);
}
}

View file

@ -4,6 +4,8 @@
import oshi.hardware.*;
import oshi.software.os.OperatingSystem;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
import pro.gravit.utils.helper.JVMHelper;
import java.util.List;
@ -104,6 +106,27 @@ public String getBaseboardSerialNumber() {
return hardware.getComputerSystem().getBaseboard().getSerialNumber();
}
public HardwareVerificationFeatureAPI.HardwareStatisticData getStatisticData() {
return new HardwareVerificationFeatureAPI.HardwareStatisticData(
JVMHelper.ARCH.toHardwareFeatureArch(JVMHelper.ARCH_TYPE),
JVMHelper.OS.toHardwareFeatureOs(JVMHelper.OS_TYPE),
getTotalMemory(),
getProcessorLogicalCount(),
getProcessorPhysicalCount(),
getProcessorMaxFreq(),
isBattery(),
getGraphicCardName()
);
}
public HardwareVerificationFeatureAPI.HardwareIdentifyData getIdentifyData() {
return new HardwareVerificationFeatureAPI.HardwareIdentifyData(
getBaseboardSerialNumber(),
getHWDiskID(),
getDisplayID()
);
}
public HardwareReportRequest.HardwareInfo getHardwareInfo(boolean needSerial) {
HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo();
info.bitness = getBitness();

View file

@ -1,10 +1,11 @@
package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
import java.util.Set;
public class AssetUploadInfoRequestEvent extends RequestEvent {
public class AssetUploadInfoRequestEvent extends RequestEvent implements TextureUploadFeatureAPI.TextureUploadInfo {
public Set<String> available;
public SlimSupportConf slimSupportConf;
@ -18,6 +19,16 @@ public String getType() {
return "assetUploadInfo";
}
@Override
public Set<String> getAvailable() {
return available;
}
@Override
public boolean isRequireManualSlimSkinSelect() {
return slimSupportConf == SlimSupportConf.USER;
}
public enum SlimSupportConf {
UNSUPPORTED, USER, SERVER
}

View file

@ -1,8 +1,9 @@
package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
public class GetSecureLevelInfoRequestEvent extends RequestEvent {
public class GetSecureLevelInfoRequestEvent extends RequestEvent implements HardwareVerificationFeatureAPI.SecurityLevelInfo {
public final byte[] verifySecureKey;
public boolean enabled;
@ -19,4 +20,14 @@ public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey, boolean enabled) {
public String getType() {
return "getSecureLevelInfo";
}
@Override
public boolean isRequired() {
return enabled;
}
@Override
public byte[] getSignData() {
return verifySecureKey;
}
}

View file

@ -2,8 +2,9 @@
import pro.gravit.launcher.base.events.ExtendedTokenRequestEvent;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
public class VerifySecureLevelKeyRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent {
public class VerifySecureLevelKeyRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent, HardwareVerificationFeatureAPI.SecurityLevelVerification {
public boolean needHardwareInfo;
public boolean onlyStatisticInfo;
public String extendedToken;
@ -42,4 +43,17 @@ public String getExtendedToken() {
public long getExtendedTokenExpire() {
return expire;
}
@Override
public HardwareCollectLevel getHardwareCollectLevel() {
if(needHardwareInfo) {
if(onlyStatisticInfo) {
return HardwareCollectLevel.ONLY_STATISTIC;
} else {
return HardwareCollectLevel.ALL;
}
} else {
return HardwareCollectLevel.NONE;
}
}
}

View file

@ -295,6 +295,11 @@ public UUID getUUID() {
return uuid;
}
@Override
public String getMinecraftVersion() {
return version.toString();
}
@Override
public String getDescription() {
return info;

View file

@ -1,35 +1,50 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
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.cabinet.AssetUploadInfoRequest;
import pro.gravit.launcher.base.request.cabinet.GetAssetUploadUrl;
import pro.gravit.launcher.base.request.secure.GetSecureLevelInfoRequest;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.request.secure.VerifySecureLevelKeyRequest;
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.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.*;
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.Texture;
import pro.gravit.launcher.core.api.model.User;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
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 {
public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI, TextureUploadFeatureAPI, HardwareVerificationFeatureAPI {
private final RequestService request;
private final String authId;
private final HttpClient client = HttpClient.newBuilder().build();
public RequestFeatureAPIImpl(RequestService request, String authId) {
this.request = request;
@ -48,7 +63,10 @@ public CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword pas
connectType = AuthRequest.ConnectTypes.CLIENT;
}
return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, connectType))
.thenApply(response -> new AuthResponse(response.makeUserInfo(), response.oauth));
.thenApply(response -> {
Request.setOAuth(authId, response.oauth);
return new AuthResponse(response.makeUserInfo(), response.oauth);
});
}
private AuthRequest.AuthPasswordInterface convertAuthPasswordAll(AuthMethodPassword password) {
@ -152,6 +170,7 @@ public CompletableFuture<SelfUser> restore(String accessToken, boolean fetchUser
}
return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> {
// TODO: invalidToken process
Request.setOAuth(authId, new AuthRequestEvent.OAuthRequestEvent(accessToken, null, 0));
return e.userInfo;
});
}
@ -177,6 +196,100 @@ public CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName) {
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));
}
@Override
public CompletableFuture<TextureUploadInfo> fetchInfo() {
return request.request(new AssetUploadInfoRequest()).thenApply(response -> response);
}
@Override
public CompletableFuture<Texture> upload(String name, byte[] bytes, UploadSettings settings) {
return request.request(new GetAssetUploadUrl(name)).thenCompose((response) -> {
String accessToken = response.token == null ? Request.getAccessToken() : response.token.accessToken;
String boundary = SecurityHelper.toHex(SecurityHelper.randomBytes(32));
String jsonOptions = settings == null ? "{}" : Launcher.gsonManager.gson.toJson(new TextureUploadOptions(settings.slim()));
byte[] preFileData;
try(ByteArrayOutputStream output = new ByteArrayOutputStream(256)) {
output.write("--".getBytes(StandardCharsets.UTF_8));
output.write(boundary.getBytes(StandardCharsets.UTF_8));
output.write("\r\nContent-Disposition: form-data; name=\"options\"\r\nContent-Type: application/json\r\n\r\n".getBytes(StandardCharsets.UTF_8));
output.write(jsonOptions.getBytes(StandardCharsets.UTF_8));
output.write("\r\n--".getBytes(StandardCharsets.UTF_8));
output.write(boundary.getBytes(StandardCharsets.UTF_8));
output.write("\r\nContent-Disposition: form-data; name=\"file\"; filename=\"file\"\r\nContent-Type: image/png\r\n\r\n".getBytes(StandardCharsets.UTF_8));
preFileData = output.toByteArray();
} catch (IOException ex) {
return CompletableFuture.failedFuture(ex);
}
byte[] postFileData;
try(ByteArrayOutputStream output = new ByteArrayOutputStream(128)) {
output.write("\r\n--".getBytes(StandardCharsets.UTF_8));
output.write(boundary.getBytes(StandardCharsets.UTF_8));
output.write("--\r\n".getBytes(StandardCharsets.UTF_8));
postFileData = output.toByteArray();
} catch (IOException ex) {
return CompletableFuture.failedFuture(ex);
}
return client.sendAsync(HttpRequest.newBuilder()
.uri(URI.create(response.url))
.POST(HttpRequest.BodyPublishers.concat(HttpRequest.BodyPublishers.ofByteArray(preFileData),
HttpRequest.BodyPublishers.ofByteArray(bytes),
HttpRequest.BodyPublishers.ofByteArray(postFileData)))
.header("Authorization", "Bearer "+accessToken)
.header("Content-Type", "multipart/form-data; boundary=\""+boundary+"\"")
.header("Accept", "application/json")
.build(), HttpResponse.BodyHandlers.ofByteArray());
}).thenCompose((response) -> {
if(response.statusCode() >= 200 && response.statusCode() < 300) {
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(response.body()))) {
return CompletableFuture.completedFuture(Launcher.gsonManager.gson.fromJson(reader, UserTexture.class).toLauncherTexture());
} catch (Throwable e) {
return CompletableFuture.failedFuture(e);
}
} else {
try(Reader reader = new InputStreamReader(new ByteArrayInputStream(response.body()))) {
UploadError error = Launcher.gsonManager.gson.fromJson(reader, UploadError.class);
return CompletableFuture.failedFuture(new RequestException(error.error()));
} catch (Exception ex) {
return CompletableFuture.failedFuture(ex);
}
}
});
}
@Override
public CompletableFuture<SecurityLevelInfo> getSecurityInfo() {
return request.request(new GetSecureLevelInfoRequest()).thenApply(response -> response);
}
@Override
public CompletableFuture<SecurityLevelVerification> privateKeyVerification(PublicKey publicKey, byte[] signature) {
return request.request(new VerifySecureLevelKeyRequest(publicKey.getEncoded(), signature)).thenApply(response -> response);
}
@Override
public CompletableFuture<Void> sendHardwareInfo(HardwareStatisticData statisticData, HardwareIdentifyData identifyData) {
if(statisticData == null && identifyData == null) { // Hardware info token special
return request.request(new HardwareReportRequest()).thenApply(response -> null);
} else {
var hardwareInfo = new HardwareReportRequest.HardwareInfo();
if(statisticData != null) {
hardwareInfo.bitness = statisticData.arch() == Arch.X86 || statisticData.arch() == Arch.ARM32 ? 32 : 64;
hardwareInfo.totalMemory = statisticData.totalPhysicalMemory();
hardwareInfo.logicalProcessors = statisticData.logicalProcessors();
hardwareInfo.physicalProcessors = statisticData.physicalProcessors();
hardwareInfo.processorMaxFreq = statisticData.processorMaxFreq();
hardwareInfo.battery = statisticData.battery();
hardwareInfo.graphicCard = statisticData.graphicCard();
}
if(identifyData != null) {
hardwareInfo.hwDiskId = identifyData.persistentStorageId();
hardwareInfo.displayId = identifyData.edid();
hardwareInfo.baseboardSerialNumber = identifyData.baseboardSerialNumber();
}
return request.request(new HardwareReportRequest(hardwareInfo)).thenApply(response -> null);
}
}
public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {
@Override
public HashedDir getHashedDir() {
@ -188,4 +301,19 @@ public String getUrl() {
return url;
}
}
public record TextureUploadOptions(boolean modelSlim) {
}
public record UserTexture(@LauncherNetworkAPI String url, @LauncherNetworkAPI String digest, @LauncherNetworkAPI Map<String, String> metadata) {
Texture toLauncherTexture() {
return new pro.gravit.launcher.base.profiles.Texture(url, SecurityHelper.fromHex(digest), metadata);
}
}
public record UploadError(@LauncherNetworkAPI String error) {
}
}

View file

@ -8,6 +8,13 @@
public class HardwareReportRequest extends Request<HardwareReportRequestEvent> {
public HardwareInfo hardware;
public HardwareReportRequest() {
}
public HardwareReportRequest(HardwareInfo hardware) {
this.hardware = hardware;
}
@Override
public String getType() {
return "hardwareReport";

View file

@ -0,0 +1,57 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.utils.helper.JVMHelper;
import java.security.PublicKey;
import java.util.concurrent.CompletableFuture;
public interface HardwareVerificationFeatureAPI extends FeatureAPI {
CompletableFuture<SecurityLevelInfo> getSecurityInfo();
CompletableFuture<SecurityLevelVerification> privateKeyVerification(PublicKey publicKey, byte[] signature);
CompletableFuture<Void> sendHardwareInfo(HardwareStatisticData statisticData, HardwareIdentifyData identifyData);
interface SecurityLevelInfo {
boolean isRequired();
byte[] getSignData();
}
interface SecurityLevelVerification {
HardwareCollectLevel getHardwareCollectLevel();
enum HardwareCollectLevel {
NONE, ONLY_STATISTIC, ALL
}
}
record HardwareStatisticData(Arch arch, Os os, long totalPhysicalMemory,
int logicalProcessors, int physicalProcessors,
long processorMaxFreq, boolean battery,
String graphicCard) {
}
record HardwareIdentifyData(String baseboardSerialNumber, String persistentStorageId,
byte[] edid) {
}
enum Arch {
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
public final String name;
Arch(String name) {
this.name = name;
}
}
enum Os {
WINDOWS("windows"), LINUX("linux"), MACOS("macos");
public final String name;
Os(String name) {
this.name = name;
}
}
}

View file

@ -21,6 +21,7 @@ interface UpdateInfo {
interface ClientProfile {
String getName();
UUID getUUID();
String getMinecraftVersion();
String getDescription();
List<OptionalMod> getOptionalMods();
String getProperty(String name);

View file

@ -5,7 +5,8 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public interface TextureUploadFeatureAPI {
public interface TextureUploadFeatureAPI extends FeatureAPI {
String FEATURE_NAME = "assetupload";
CompletableFuture<TextureUploadInfo> fetchInfo();
CompletableFuture<Texture> upload(String name, byte[] bytes, UploadSettings settings);

View file

@ -1,5 +1,7 @@
package pro.gravit.utils.helper;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
@ -56,7 +58,7 @@ public static int getBuild() {
return Runtime.version().update();
}
public static String getNativeExtension(JVMHelper.OS OS_TYPE) {
public static String getNativeExtension(OS OS_TYPE) {
return switch (OS_TYPE) {
case MUSTDIE -> ".dll";
case LINUX -> ".so";
@ -64,7 +66,7 @@ public static String getNativeExtension(JVMHelper.OS OS_TYPE) {
};
}
public static String getNativePrefix(JVMHelper.OS OS_TYPE) {
public static String getNativePrefix(OS OS_TYPE) {
return switch (OS_TYPE) {
case LINUX, MACOSX -> "lib";
default -> "";
@ -124,6 +126,15 @@ public enum ARCH {
public final String name;
public static HardwareVerificationFeatureAPI.Arch toHardwareFeatureArch(ARCH arch) {
return switch (arch) {
case X86 -> HardwareVerificationFeatureAPI.Arch.X86;
case X86_64 -> HardwareVerificationFeatureAPI.Arch.X86_64;
case ARM64 -> HardwareVerificationFeatureAPI.Arch.ARM64;
case ARM32 -> HardwareVerificationFeatureAPI.Arch.ARM32;
};
}
ARCH(String name) {
this.name = name;
}
@ -138,6 +149,14 @@ public enum OS {
this.name = name;
}
public static HardwareVerificationFeatureAPI.Os toHardwareFeatureOs(OS os) {
return switch (os) {
case MUSTDIE -> HardwareVerificationFeatureAPI.Os.WINDOWS;
case LINUX -> HardwareVerificationFeatureAPI.Os.LINUX;
case MACOSX -> HardwareVerificationFeatureAPI.Os.MACOS;
};
}
public static OS byName(String name) {
if (name.startsWith("Windows"))
return MUSTDIE;

View file

@ -1,4 +1,6 @@
# Modification of the launcher sashok724's v3 from Gravit [![Build Status](https://travis-ci.com/GravitLauncher/Launcher.svg?branch=master)](https://travis-ci.com/GravitLauncher/Launcher)
# Modification of the launcher sashok724's v3 from Gravit
[![Build Status](https://travis-ci.com/GravitLauncher/Launcher.svg?branch=master)](https://travis-ci.com/GravitLauncher/Launcher)
[![Join our Discord](https://img.shields.io/discord/853340557522370561.svg?logo=discord&label=)](https://discord.gg/b9QG4ygY75)
* [Discord channel](https://discord.gg/b9QG4ygY75)
@ -9,4 +11,4 @@
```sh
curl -s https://raw.githubusercontent.com/GravitLauncher/Launcher/master/get_it.sh | sh
```
```