Compare commits

...

5 commits

Author SHA1 Message Date
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
18 changed files with 420 additions and 128 deletions

View file

@ -6,18 +6,14 @@
import pro.gravit.launcher.client.*; import pro.gravit.launcher.client.*;
import pro.gravit.launcher.core.api.LauncherAPI; import pro.gravit.launcher.core.api.LauncherAPI;
import pro.gravit.launcher.core.api.LauncherAPIHolder; import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI; import pro.gravit.launcher.core.api.features.*;
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.core.backend.LauncherBackendAPIHolder;
import pro.gravit.launcher.runtime.backend.LauncherBackendImpl; import pro.gravit.launcher.runtime.backend.LauncherBackendImpl;
import pro.gravit.launcher.runtime.client.*; import pro.gravit.launcher.runtime.client.*;
import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase; import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase;
import pro.gravit.launcher.client.events.ClientExitPhase; import pro.gravit.launcher.client.events.ClientExitPhase;
import pro.gravit.launcher.runtime.client.events.ClientPreGuiPhase; 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.ModulesCommand;
import pro.gravit.launcher.runtime.console.SignDataCommand;
import pro.gravit.launcher.runtime.gui.NoRuntimeProvider; import pro.gravit.launcher.runtime.gui.NoRuntimeProvider;
import pro.gravit.launcher.runtime.gui.RuntimeProvider; import pro.gravit.launcher.runtime.gui.RuntimeProvider;
import pro.gravit.launcher.runtime.managers.ConsoleManager; import pro.gravit.launcher.runtime.managers.ConsoleManager;
@ -33,15 +29,8 @@
import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.start.RuntimeModuleManager;
import pro.gravit.utils.helper.*; import pro.gravit.utils.helper.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.cert.X509Certificate; 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.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -54,8 +43,6 @@ public class LauncherEngine {
// Instance // Instance
private final AtomicBoolean started = new AtomicBoolean(false); private final AtomicBoolean started = new AtomicBoolean(false);
public RuntimeProvider runtimeProvider; public RuntimeProvider runtimeProvider;
public ECPublicKey publicKey;
public ECPrivateKey privateKey;
public Class<? extends RuntimeProvider> basicRuntimeProvider; public Class<? extends RuntimeProvider> basicRuntimeProvider;
private LauncherEngine(boolean clientInstance, 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); 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 { public void start(String... args) throws Throwable {
//Launcher.modulesManager = new ClientModuleManager(this); //Launcher.modulesManager = new ClientModuleManager(this);
ClientPreGuiPhase event = new ClientPreGuiPhase(null); ClientPreGuiPhase event = new ClientPreGuiPhase(null);
@ -256,14 +213,15 @@ public void start(String... args) throws Throwable {
return new LauncherAPI(Map.of( return new LauncherAPI(Map.of(
AuthFeatureAPI.class, impl, AuthFeatureAPI.class, impl,
UserFeatureAPI.class, impl, UserFeatureAPI.class, impl,
ProfileFeatureAPI.class, impl)); ProfileFeatureAPI.class, impl,
TextureUploadFeatureAPI.class, impl,
HardwareVerificationFeatureAPI.class, impl));
}); });
LauncherBackendAPIHolder.setApi(new LauncherBackendImpl()); LauncherBackendAPIHolder.setApi(new LauncherBackendImpl());
// //
Objects.requireNonNull(args, "args"); Objects.requireNonNull(args, "args");
if (started.getAndSet(true)) if (started.getAndSet(true))
throw new IllegalStateException("Launcher has been already started"); throw new IllegalStateException("Launcher has been already started");
readKeys();
registerCommands(); registerCommands();
LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this)); LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this));
runtimeProvider.preLoad(); runtimeProvider.preLoad();
@ -272,8 +230,6 @@ public void start(String... args) throws Throwable {
} }
private void registerCommands() { private void registerCommands() {
ConsoleManager.handler.registerCommand("getpublickey", new GetPublicKeyCommand(this));
ConsoleManager.handler.registerCommand("signdata", new SignDataCommand(this));
ConsoleManager.handler.registerCommand("modules", new ModulesCommand()); ConsoleManager.handler.registerCommand("modules", new ModulesCommand());
} }
} }

View file

@ -115,6 +115,9 @@ CompletableFuture<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.U
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING); callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING);
if(!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
HashedDir realFiles = new HashedDir(targetDir, matcher, false, true); HashedDir realFiles = new HashedDir(targetDir, matcher, false, true);
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF); callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DIFF);
return updateInfo.getHashedDir().diff(realFiles, matcher); 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.ClientPermissions;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.core.api.LauncherAPIHolder; import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI; import pro.gravit.launcher.core.api.features.*;
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.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword; import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser; 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.UserSettings;
import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException; import pro.gravit.launcher.core.backend.exceptions.LauncherBackendException;
import pro.gravit.launcher.core.backend.extensions.Extension; 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.NewLauncherSettings;
import pro.gravit.launcher.runtime.client.DirBridge; import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.client.ServerPinger; import pro.gravit.launcher.runtime.client.ServerPinger;
import pro.gravit.launcher.runtime.debug.DebugMain; import pro.gravit.launcher.runtime.debug.DebugMain;
import pro.gravit.launcher.runtime.managers.SettingsManager; import pro.gravit.launcher.runtime.managers.SettingsManager;
import pro.gravit.launcher.runtime.utils.HWIDProvider;
import pro.gravit.launcher.runtime.utils.LauncherUpdater; import pro.gravit.launcher.runtime.utils.LauncherUpdater;
import pro.gravit.utils.helper.JavaHelper; import pro.gravit.utils.helper.JavaHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -39,7 +41,7 @@
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Function; import java.util.function.Function;
public class LauncherBackendImpl implements LauncherBackendAPI { public class LauncherBackendImpl implements LauncherBackendAPI, TextureUploadExtension {
private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this); private final ClientDownloadImpl clientDownloadImpl = new ClientDownloadImpl(this);
private volatile MainCallback callback; private volatile MainCallback callback;
ExecutorService executorService; ExecutorService executorService;
@ -48,12 +50,15 @@ public class LauncherBackendImpl implements LauncherBackendAPI {
private SettingsManager settingsManager; private SettingsManager settingsManager;
private NewLauncherSettings allSettings; private NewLauncherSettings allSettings;
private BackendSettings backendSettings; private BackendSettings backendSettings;
// Hardware
private volatile ECKeyHolder ecKeyHolder;
// Data // Data
private volatile List<ProfileFeatureAPI.ClientProfile> profiles; private volatile List<ProfileFeatureAPI.ClientProfile> profiles;
private volatile UserPermissions permissions; private volatile UserPermissions permissions;
private volatile SelfUser selfUser; private volatile SelfUser selfUser;
private volatile List<Java> availableJavas; private volatile List<Java> availableJavas;
private volatile CompletableFuture<List<Java>> availableJavasFuture; private volatile CompletableFuture<List<Java>> availableJavasFuture;
private volatile CompletableFuture<Void> processHardwareFuture;
private final Map<UUID, CompletableFuture<ServerPingInfo>> pingFutures = new ConcurrentHashMap<>(); private final Map<UUID, CompletableFuture<ServerPingInfo>> pingFutures = new ConcurrentHashMap<>();
@Override @Override
@ -74,6 +79,8 @@ private void doInit() throws Exception {
allSettings = settingsManager.getConfig(); allSettings = settingsManager.getConfig();
backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings()); backendSettings = (BackendSettings) getUserSettings("backend", (k) -> new BackendSettings());
permissions = new ClientPermissions(); permissions = new ClientPermissions();
ecKeyHolder = new ECKeyHolder();
ecKeyHolder.readKeys();
DirBridge.dirUpdates = DirBridge.defaultUpdatesDir; DirBridge.dirUpdates = DirBridge.defaultUpdatesDir;
} }
@ -157,6 +164,9 @@ private void onAuthorize(SelfUser selfUser) {
this.selfUser = selfUser; this.selfUser = selfUser;
permissions = selfUser.getPermissions(); permissions = selfUser.getPermissions();
callback.onAuthorize(selfUser); callback.onAuthorize(selfUser);
if(processHardwareFuture == null) {
processHardwareFuture = processHardware();
}
} }
@Override @Override
@ -285,8 +295,14 @@ public boolean isTestMode() {
} }
} }
@SuppressWarnings("unchecked")
@Override @Override
public <T extends Extension> T getExtension(Class<T> clazz) { 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; 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 @LauncherNetworkAPI
private String saveJavaPath; private String saveJavaPath;
transient OptionalView view; transient OptionalView view;
transient JavaHelper.JavaVersion selectedJava; transient volatile JavaHelper.JavaVersion selectedJava;
public ProfileSettingsImpl() { public ProfileSettingsImpl() {
} }
@ -203,5 +203,21 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {
} }
enableOptional(opt, (var1, var2) -> {}); 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.hardware.*;
import oshi.software.os.OperatingSystem; import oshi.software.os.OperatingSystem;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest; 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; import java.util.List;
@ -104,6 +106,27 @@ public String getBaseboardSerialNumber() {
return hardware.getComputerSystem().getBaseboard().getSerialNumber(); 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) { public HardwareReportRequest.HardwareInfo getHardwareInfo(boolean needSerial) {
HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo(); HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo();
info.bitness = getBitness(); info.bitness = getBitness();

View file

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

View file

@ -1,8 +1,9 @@
package pro.gravit.launcher.base.events.request; package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.events.RequestEvent; 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 final byte[] verifySecureKey;
public boolean enabled; public boolean enabled;
@ -19,4 +20,14 @@ public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey, boolean enabled) {
public String getType() { public String getType() {
return "getSecureLevelInfo"; 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.ExtendedTokenRequestEvent;
import pro.gravit.launcher.base.events.RequestEvent; 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 needHardwareInfo;
public boolean onlyStatisticInfo; public boolean onlyStatisticInfo;
public String extendedToken; public String extendedToken;
@ -42,4 +43,17 @@ public String getExtendedToken() {
public long getExtendedTokenExpire() { public long getExtendedTokenExpire() {
return expire; 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; return uuid;
} }
@Override
public String getMinecraftVersion() {
return version.toString();
}
@Override @Override
public String getDescription() { public String getDescription() {
return info; return info;

View file

@ -1,35 +1,50 @@
package pro.gravit.launcher.base.request; package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.Launcher; 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.profiles.ClientProfile;
import pro.gravit.launcher.base.request.auth.*; import pro.gravit.launcher.base.request.auth.*;
import pro.gravit.launcher.base.request.auth.password.*; 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.ProfilesRequest;
import pro.gravit.launcher.base.request.update.UpdateRequest; import pro.gravit.launcher.base.request.update.UpdateRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest; import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest; import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI; import pro.gravit.launcher.core.api.features.*;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethodPassword; 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.AuthChainPassword;
import pro.gravit.launcher.core.api.method.password.AuthOAuthPassword; 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.AuthPlainPassword;
import pro.gravit.launcher.core.api.method.password.AuthTotpPassword; import pro.gravit.launcher.core.api.method.password.AuthTotpPassword;
import pro.gravit.launcher.core.api.model.SelfUser; 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.api.model.User;
import pro.gravit.launcher.core.hasher.HashedDir; import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.utils.helper.SecurityHelper; 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.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; 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 RequestService request;
private final String authId; private final String authId;
private final HttpClient client = HttpClient.newBuilder().build();
public RequestFeatureAPIImpl(RequestService request, String authId) { public RequestFeatureAPIImpl(RequestService request, String authId) {
this.request = request; this.request = request;
@ -48,7 +63,10 @@ public CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword pas
connectType = AuthRequest.ConnectTypes.CLIENT; connectType = AuthRequest.ConnectTypes.CLIENT;
} }
return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, connectType)) 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) { 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 -> { return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> {
// TODO: invalidToken process // TODO: invalidToken process
Request.setOAuth(authId, new AuthRequestEvent.OAuthRequestEvent(accessToken, null, 0));
return e.userInfo; 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)); 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 { public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {
@Override @Override
public HashedDir getHashedDir() { public HashedDir getHashedDir() {
@ -188,4 +301,19 @@ public String getUrl() {
return url; 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 class HardwareReportRequest extends Request<HardwareReportRequestEvent> {
public HardwareInfo hardware; public HardwareInfo hardware;
public HardwareReportRequest() {
}
public HardwareReportRequest(HardwareInfo hardware) {
this.hardware = hardware;
}
@Override @Override
public String getType() { public String getType() {
return "hardwareReport"; 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 { interface ClientProfile {
String getName(); String getName();
UUID getUUID(); UUID getUUID();
String getMinecraftVersion();
String getDescription(); String getDescription();
List<OptionalMod> getOptionalMods(); List<OptionalMod> getOptionalMods();
String getProperty(String name); String getProperty(String name);

View file

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

View file

@ -1,5 +1,7 @@
package pro.gravit.utils.helper; package pro.gravit.utils.helper;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean; import java.lang.management.OperatingSystemMXBean;
@ -56,7 +58,7 @@ public static int getBuild() {
return Runtime.version().update(); return Runtime.version().update();
} }
public static String getNativeExtension(JVMHelper.OS OS_TYPE) { public static String getNativeExtension(OS OS_TYPE) {
return switch (OS_TYPE) { return switch (OS_TYPE) {
case MUSTDIE -> ".dll"; case MUSTDIE -> ".dll";
case LINUX -> ".so"; 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) { return switch (OS_TYPE) {
case LINUX, MACOSX -> "lib"; case LINUX, MACOSX -> "lib";
default -> ""; default -> "";
@ -124,6 +126,15 @@ public enum ARCH {
public final String name; 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) { ARCH(String name) {
this.name = name; this.name = name;
} }
@ -138,6 +149,14 @@ public enum OS {
this.name = name; 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) { public static OS byName(String name) {
if (name.startsWith("Windows")) if (name.startsWith("Windows"))
return MUSTDIE; return MUSTDIE;