From 324de7226dd675abbeb694eabe4df10d0bb89ddd Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:24:39 +0700 Subject: [PATCH] [FEATURE] Support Runtime encrypt and themes part 1 --- .../runtime/backend/EncryptedVfsFile.java | 50 +++++++++++++++++++ .../runtime/backend/LauncherBackendImpl.java | 38 ++++++++++++++ .../launcher/base/vfs/file/CachedVfsFile.java | 45 +++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 Launcher/src/main/java/pro/gravit/launcher/runtime/backend/EncryptedVfsFile.java create mode 100644 LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/CachedVfsFile.java diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/EncryptedVfsFile.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/EncryptedVfsFile.java new file mode 100644 index 00000000..61df6f1c --- /dev/null +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/backend/EncryptedVfsFile.java @@ -0,0 +1,50 @@ +package pro.gravit.launcher.runtime.backend; + +import pro.gravit.launcher.base.Launcher; +import pro.gravit.launcher.base.vfs.Vfs; +import pro.gravit.launcher.base.vfs.VfsFile; +import pro.gravit.utils.helper.SecurityHelper; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class EncryptedVfsFile extends VfsFile { + private VfsFile parent; + private final String alg; + private final SecretKeySpec sKeySpec; + private final IvParameterSpec iKeySpec; + + public EncryptedVfsFile(VfsFile parent) { + this.parent = parent; + this.alg = "AES/CBC/PKCS5Padding"; + try { + byte[] compat = SecurityHelper.getAESKey(Launcher.getConfig().runtimeEncryptKey.getBytes(StandardCharsets.UTF_8)); + sKeySpec = new SecretKeySpec(compat, "AES"); + iKeySpec = new IvParameterSpec("8u3d90ikr7o67lsq".getBytes()); + } catch (Exception e) { + throw new SecurityException(e); + } + } + + @Override + public InputStream getInputStream() { + Cipher cipher; + try { + cipher = Cipher.getInstance(alg); + cipher.init(Cipher.DECRYPT_MODE, sKeySpec, iKeySpec); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | + InvalidAlgorithmParameterException e) { + throw new SecurityException(e); + } + return new BufferedInputStream(new CipherInputStream(parent.getInputStream(), cipher)); + } +} 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 903f5175..4b9779ae 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 @@ -1,7 +1,12 @@ package pro.gravit.launcher.runtime.backend; import pro.gravit.launcher.base.ClientPermissions; +import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.profiles.ClientProfile; +import pro.gravit.launcher.base.vfs.Vfs; +import pro.gravit.launcher.base.vfs.directory.FileVfsDirectory; +import pro.gravit.launcher.base.vfs.file.CachedVfsFile; +import pro.gravit.launcher.base.vfs.file.UrlVfsFile; import pro.gravit.launcher.core.api.LauncherAPIHolder; import pro.gravit.launcher.core.api.features.*; import pro.gravit.launcher.core.api.method.AuthMethod; @@ -22,12 +27,15 @@ 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.IOHelper; 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; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -370,4 +378,34 @@ public CompletableFuture processHardware() { return CompletableFuture.failedFuture(new UnsupportedOperationException()); }); } + + public Path initVfsDirectory() { + Path defaultPath = Path.of("runtime"); + if(isTestMode()) { + Vfs.get().put(defaultPath, new FileVfsDirectory(defaultPath)); + } else { + var encryptKey = Launcher.getConfig().runtimeEncryptKey; + if(encryptKey == null) { + for(var e : Launcher.getConfig().runtime.entrySet()) { + var realPath = e.getKey(); + var encodedName = "runtime/" + realPath; + try { + Vfs.get().put(defaultPath.resolve(realPath), new UrlVfsFile(IOHelper.getResourceURL(encodedName))); + } catch (NoSuchFileException ignored) { + } + } + } else { + for(var e : Launcher.getConfig().runtime.entrySet()) { + var realPath = e.getKey(); + var hash = e.getValue(); + var encodedName = "runtime/" + SecurityHelper.toHex(hash); + try { + Vfs.get().put(defaultPath.resolve(realPath), new CachedVfsFile(new EncryptedVfsFile(new UrlVfsFile(IOHelper.getResourceURL(encodedName))))); + } catch (NoSuchFileException ignored) { + } + } + } + } + return defaultPath; + } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/CachedVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/CachedVfsFile.java new file mode 100644 index 00000000..b72c3a54 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/CachedVfsFile.java @@ -0,0 +1,45 @@ +package pro.gravit.launcher.base.vfs.file; + +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.SoftReference; + +public class CachedVfsFile extends VfsFile { + private final VfsFile delegate; + private volatile SoftReference cache; + + public CachedVfsFile(VfsFile delegate) { + this.delegate = delegate; + } + + private synchronized InputStream tryCache() { + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try(InputStream input = delegate.getInputStream()) { + input.transferTo(output); + } + byte[] bytes = output.toByteArray(); + cache = new SoftReference<>(bytes); + return new ByteArrayInputStream(bytes); + } catch (OutOfMemoryError | IOException ignored) { + } + return null; + } + + @Override + public InputStream getInputStream() { + var cachedBytes = cache == null ? null : cache.get(); + if(cachedBytes != null) { + return new ByteArrayInputStream(cachedBytes); + } + var cached = tryCache(); + if(cached != null) { + return cached; + } + return delegate.getInputStream(); + } +}