Compare commits

...

60 commits

Author SHA1 Message Date
Metall
bcdd43fef6
Merge 05530b6664 into b1929f7927 2025-06-30 14:24:10 +00:00
Gravita
b1929f7927 [FIX] Bug fixes 2025-06-29 06:02:59 +07:00
Gravita
c46503fa95 [FIX] NPE with profile.verify 2025-06-29 05:42:36 +07:00
Gravita
8421da7772 [FIX] NPE with profile.verify 2025-06-29 05:38:03 +07:00
Gravita
26c21a3ec7 [FIX] pushUpdate: UnsupportedOperationException 2025-06-29 04:30:39 +07:00
microwin7
bd0423460f [ANY] Renames proguard directory for libraries
Renames the 'proguard' directory to 'proguard-libraries'
for better clarity and organization of library-related
Proguard files.
2025-06-28 21:49:39 +03:00
Gravita
846ab77795 [FIX] Fix verifyError with buildNumber property 2025-06-28 23:42:36 +07:00
Gravita
bda6a35945 [FEATURE][REFACTOR] New launcher build pipeline 2025-06-28 23:02:47 +07:00
Gravita
101d8c7275 [FIX] Probably fix access profileMap with multiple threads 2025-06-28 21:40:50 +07:00
Antoni
b149686abd
[DOCS][FIX] Update SecurityCheckCommand 2025-06-28 12:55:16 +03:00
Gravita
38dcc1a53e [FIX] Try fix create assets directory 2025-06-26 23:16:19 +07:00
Gravita
324de7226d [FEATURE] Support Runtime encrypt and themes part 1 2025-06-22 19:24:39 +07:00
Gravita
37546e8a5a [FEATURE] Support OverlayVfsDirectory 2025-06-22 18:38:37 +07:00
Gravita
110c2a089e [FEATURE] Backport vfs system from 6.0.0 2025-06-22 18:22:00 +07:00
Gravita
6013f75091 [FIX] NeoForge 1.21.6 2025-06-22 18:09:04 +07:00
Gravita
04ecbfc14b [FEATURE] BuildInParams 2025-06-22 18:08:24 +07:00
Gravita
68e2230d34 [FIX] Support profile limited 2025-06-22 17:58:34 +07:00
Gravita
183d0fc9df Merge hotfix/5.6.16 2025-06-21 20:02:17 +07:00
Gravita
e1ee1099cc Merge branch 'hotfix/5.6.16' 2025-06-21 20:00:49 +07:00
Gravita
b0e840a040 [FIX] ModuleConf resolve mudule path, not class path 2025-06-21 19:59:11 +07:00
Gravita
0de8ff6e14 [FIX] Some bug fixes 2025-06-21 18:37:28 +07:00
Gravita
b9c6472e2a [FIX] Save profile after create 2025-06-21 17:58:57 +07:00
Gravita
230fd22f99 [FIX] Add .json ext to profile files 2025-06-21 17:52:31 +07:00
Gravita
d2a8bc2c35 [FIX] Fix download assets 2025-06-21 17:47:44 +07:00
Gravita
ab1fa64f7a [FIX] Fix sync command 2025-06-21 17:00:36 +07:00
Gravita
a959414c64 [FEATURE] Use buildSecrets in UpdatesProvider 2025-06-18 00:08:22 +07:00
Gravita
2d046f7d7b [ANY] Use 5.7.x mirror 2025-06-14 20:48:07 +07: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
Gravita
2470780591 [FIX] minecraftAccessToken is null after restore 2025-06-12 16:09:23 +07:00
Gravita
31f919021c [FIX] Bug fixes 2025-06-12 15:48:00 +07:00
Gravita
463d381180 [ANY] 5.7.0-dev 2025-06-12 13:30:08 +07:00
Gravita
03b7f35418 [FIX] Dont create files in test env 2025-06-12 13:28:48 +07:00
Gravita
6110a29f5c [FEATURE][EXPERIMENTAL] Merge experimental/5.7.x 2025-06-11 21:34:33 +07:00
Gravita
88a70bf47b [FEATURE][EXPERIMENTAL] Refactor ProfilesProvider 2025-06-11 21:09:38 +07:00
Gravita
88fa3ca1a0 [FEATURE] Add url to HashedFile 2025-06-02 13:24:45 +07:00
Gravita
c65e20d77b [FIX][EXPERIMENTAL] Full support UpdatesProvider 2025-05-30 16:49:55 +07:00
Gravita
e664c579a5 [FEATURE][EXPERIMENTAL] Full support UpdatesProvider 2025-05-30 16:22:31 +07:00
Gravita
1e4e1c5837 [FIX] Add writeLock for Client in launchserver.config.reload 2025-05-30 14:48:48 +07:00
Gravita
b8f6857ef2 [REFACTOR] Update create module api 2025-05-30 14:15:49 +07:00
Gravita
de844201b4 [FIX] Sentry LaunchServer module 2025-05-30 13:35:53 +07:00
Gravita
27bcfc046e
[FEATURE] Upgrade minimum version to Java 21 2024-11-24 15:54:23 +07:00
Gravita
f946c893e1
[FEATURE] Add OptionalMod.getDependencies() 2024-11-24 15:30:14 +07:00
Gravita
f1559183a1
[FIX} New API bug fixes 2024-11-08 15:33:53 +07:00
Gravita
c90a13af71
Merge with dev 2024-11-08 07:37:17 +07:00
Gravita
f297a5878d
Merge branch 'dev' 2024-07-25 22:42:35 +07:00
Metall
05530b6664
Merge branch 'GravitLauncher:master' into master 2024-07-23 20:33:25 +05:00
Gravita
d4d6491e52
[FIX][EXPERIMENTAL] API bug fixes 2024-07-05 13:52:44 +07:00
Gravita
83cc441574
[FEATURE][EXPERIMENTAL] API holders 2024-06-15 17:07:49 +07:00
Gravita
6b873b5072
[FEATURE][EXPERIMENTAL] New API: Launcher Backend 2024-06-15 16:59:47 +07:00
Gravita
44e840734c
[FIX] Compile fix 2024-06-09 16:49:34 +07:00
Gravita
3afe77bf34
[FEATURE][EXPERIMENTAL] New API: TextureUploadExtension 2024-06-09 16:21:25 +07:00
Gravita
f6b3a62497
[FEATURE][EXPERIMENTAL] New API 2024-06-09 16:03:40 +07:00
Metall
8f20cbe104
Merge branch 'GravitLauncher:master' into master 2023-03-27 20:27:50 +05:00
Metall
66d8b9d9ca
Update PasswordVerifier.java
Убрал исключения для совместимости
2022-09-21 09:29:13 +05:00
Metall
90ee90973e
Update PasswordVerifier.java
Добавил:
1. Способ верификации "django", для осуществления авторизации с помощью PBKDF2 с SHA256.
2. Исключение на отсутствующий алгоритм(NoSuchAlgorithmException)
3. Исключение на неверные ключи(InvalidKeySpecException)
2022-09-21 08:40:40 +05:00
Metall
8bf58cff18
Create DjangoPasswordVerifier.java
Верификация PBKDF2_SHA256
2022-09-21 08:32:53 +05:00
193 changed files with 4783 additions and 1693 deletions

View file

@ -129,7 +129,7 @@
tasks.register('dumpProguard', Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/proguard"
into "$buildDir/libs/proguard-libraries"
from configurations.proguardPack
}

View file

@ -2,17 +2,16 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.modules.events.ClosePhase;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.EXELauncherBinary;
import pro.gravit.launchserver.binary.JARLauncherBinary;
import pro.gravit.launchserver.binary.LauncherBinary;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
import pro.gravit.launchserver.manangers.*;
@ -27,6 +26,7 @@
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -83,10 +83,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final Path librariesDir;
public final Path controlFile;
public final Path proguardDir;
/**
* This object contains runtime configuration
*/
public final LaunchServerRuntimeConfig runtime;
/**
* Pipeline for building JAR
*/
@ -105,7 +101,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final ConfigManager configManager;
public final FeaturesManager featuresManager;
public final KeyAgreementManager keyAgreementManager;
public final UpdatesManager updatesManager;
// HWID ban + anti-brutforce
public final CertificateManager certificateManager;
// Server
@ -119,7 +114,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final int shardId;
public LaunchServerConfig config;
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
this.dir = directories.dir;
this.tmpDir = directories.tmpDir;
this.env = env;
@ -129,7 +124,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler;
this.runtime = runtimeConfig;
this.certificateManager = certificateManager;
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir;
@ -151,7 +145,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
// Print keypair fingerprints
runtime.verify();
config.verify();
// build hooks, anti-brutforce and other
@ -161,7 +154,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
configManager = new ConfigManager();
featuresManager = new FeaturesManager(this);
authManager = new AuthManager(this);
updatesManager = new UpdatesManager(this);
RestoreResponse.registerProviders(this);
config.init(ReloadType.FULL);
@ -178,7 +170,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
launcherBinary.init();
launcherEXEBinary.init();
syncLauncherBinaries();
launcherModuleLoader = new LauncherModuleLoader(this);
if (config.components != null) {
logger.debug("Init components");
@ -226,8 +217,15 @@ public void reload(ReloadType type) throws Exception {
if(!type.equals(ReloadType.NO_AUTH)) {
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
Client client = wsHandler.getClient();
if(client.auth != null) {
var lock = client.writeLock();
lock.lock();
try {
if (client.auth_id == null) {
return;
}
client.auth = config.getAuthProviderPair(client.auth_id);
} finally {
lock.unlock();
}
});
}
@ -255,7 +253,6 @@ public void invoke(String... args) throws Exception {
@Override
public void invoke(String... args) throws Exception {
launchServerConfigManager.writeConfig(config);
launchServerConfigManager.writeRuntimeConfig(runtime);
logger.info("LaunchServerConfig saved");
}
};
@ -298,6 +295,18 @@ public void checkCertificateExpired() {
}
}
public Path createTempDirectory(String name) throws IOException {
var path = tmpDir.resolve(String.format("launchserver-%s-%s", name, SecurityHelper.randomStringToken()));
Files.createDirectories(path);
return path;
}
public Path createTempFilePath(String name, String ext) throws IOException {
var path = tmpDir.resolve(String.format("launchserver-%s-%s.%s", name, SecurityHelper.randomStringToken(), ext));
IOHelper.createParentDirs(path);
return path;
}
private LauncherBinary binary() {
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
modulesManager.invokeEvent(event);
@ -308,8 +317,20 @@ private LauncherBinary binary() {
}
public void buildLauncherBinaries() throws IOException {
launcherBinary.build();
launcherEXEBinary.build();
PipelineContext launcherContext = launcherBinary.build();
PipelineContext exeContext = launcherEXEBinary.build();
UpdatesProvider.UpdateUploadInfo jarInfo = launcherContext.makeUploadInfo(UpdatesProvider.UpdateVariant.JAR);
UpdatesProvider.UpdateUploadInfo exeInfo = exeContext.makeUploadInfo(UpdatesProvider.UpdateVariant.EXE);
List<UpdatesProvider.UpdateUploadInfo> list = new ArrayList<>(2);
if(jarInfo != null) {
list.add(jarInfo);
}
if(exeInfo != null) {
list.add(exeInfo);
}
config.updatesProvider.pushUpdate(list);
launcherContext.clear();
exeContext.clear();
}
public void close() throws Exception {
@ -319,17 +340,10 @@ public void close() throws Exception {
// Close handlers & providers
config.close(ReloadType.FULL);
modulesManager.invokeEvent(new ClosePhase());
logger.info("Save LaunchServer runtime config");
launchServerConfigManager.writeRuntimeConfig(runtime);
// Print last message before death :(
logger.info("LaunchServer stopped");
}
@Deprecated
public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles();
}
@Deprecated
public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException();
@ -356,21 +370,6 @@ public void run() {
}));
CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
// Sync updates dir
CommonHelper.newThread("Profiles and updates sync", true, () -> {
try {
// Sync profiles dir
syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) {
logger.error("Updates/Profiles not synced", e);
}
}).start();
}
if (config.netty != null)
rebindNettyServerSocket();
@ -384,47 +383,6 @@ public void run() {
}
}
public void syncLauncherBinaries() throws IOException {
logger.info("Syncing launcher binaries");
// Syncing launcher binary
logger.info("Syncing launcher binary file");
if (!launcherBinary.sync()) logger.warn("Missing launcher binary file");
// Syncing launcher EXE binary
logger.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync())
logger.warn("Missing launcher EXE binary file");
}
public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir");
config.profileProvider.sync();
if (config.netty.sendProfileUpdatesEvent) {
sendUpdateProfilesEvent();
}
}
private void sendUpdateProfilesEvent() {
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
return;
}
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
Client client = handler.getClient();
if (client == null || !client.isAuth) {
return;
}
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
event.requestUUID = RequestEvent.eventUUID;
handler.service.sendObject(ch, event);
});
}
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
updatesManager.syncUpdatesDir(dirs);
}
public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
@ -454,11 +412,7 @@ public enum LaunchServerEnv {
public interface LaunchServerConfigManager {
LaunchServerConfig readConfig() throws IOException;
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
void writeConfig(LaunchServerConfig config) throws IOException;
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
}
public static class LaunchServerDirectories {

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.KeyAgreementManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
@ -11,7 +10,6 @@
public class LaunchServerBuilder {
private LaunchServerConfig config;
private LaunchServerRuntimeConfig runtimeConfig;
private CommandHandler commandHandler;
private LaunchServer.LaunchServerEnv env;
private LaunchServerModulesManager modulesManager;
@ -36,11 +34,6 @@ public LaunchServerBuilder setModulesManager(LaunchServerModulesManager modulesM
return this;
}
public LaunchServerBuilder setRuntimeConfig(LaunchServerRuntimeConfig runtimeConfig) {
this.runtimeConfig = runtimeConfig;
return this;
}
public LaunchServerBuilder setCommandHandler(CommandHandler commandHandler) {
this.commandHandler = commandHandler;
return this;
@ -77,7 +70,7 @@ public LaunchServer build() throws Exception {
if(shardId == null) {
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0"));
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
return new LaunchServer(directories, env, config, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
}
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
@ -95,19 +88,9 @@ public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override
public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException();
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
}
}

View file

@ -13,13 +13,12 @@
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
@ -32,7 +31,6 @@
import pro.gravit.utils.helper.LogHelper;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
import java.security.cert.CertificateException;
@ -49,7 +47,7 @@ public static void main(String[] args) throws Exception {
LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer");
Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile;
Path configFile;
try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider());
@ -80,7 +78,6 @@ public static void main(String[] args) throws Exception {
}
}
LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
@ -94,11 +91,6 @@ public static void main(String[] args) throws Exception {
} else {
configFile = dir.resolve("LaunchServer.json");
}
if (IOHelper.exists(dir.resolve("RuntimeLaunchServer.conf"))) {
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.conf");
} else {
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.json");
}
CommandHandler localCommandHandler;
try {
Class.forName("org.jline.terminal.Terminal");
@ -116,23 +108,12 @@ public static void main(String[] args) throws Exception {
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
if (!Files.exists(runtimeConfigFile)) {
logger.info("Reset LaunchServer runtime config file");
runtimeConfig = new LaunchServerRuntimeConfig();
runtimeConfig.reset();
} else {
logger.info("Reading LaunchServer runtime config file");
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
runtimeConfig = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
}
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile);
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)
.setEnv(env)
.setCommandHandler(localCommandHandler)
.setRuntimeConfig(runtimeConfig)
.setConfig(config)
.setModulesManager(modulesManager)
.setLaunchServerConfigManager(launchServerConfigManager)
@ -179,7 +160,7 @@ public static void registerAll() {
OptionalAction.registerProviders();
OptionalTrigger.registerProviders();
MixProvider.registerProviders();
ProfileProvider.registerProviders();
ProfilesProvider.registerProviders();
UpdatesProvider.registerProviders();
}
@ -270,11 +251,9 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
private final Path configFile;
private final Path runtimeConfigFile;
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
public BasicLaunchServerConfigManager(Path configFile) {
this.configFile = configFile;
this.runtimeConfigFile = runtimeConfigFile;
}
@Override
@ -286,15 +265,6 @@ public LaunchServerConfig readConfig() throws IOException {
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
@ -310,21 +280,5 @@ public void writeConfig(LaunchServerConfig config) throws IOException {
IOHelper.write(configFile, bytes);
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
}
}
}
}

View file

@ -0,0 +1,42 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class DjangoPasswordVerifier extends PasswordVerifier {
public final Integer DEFAULT_ITERATIONS = 10000;
private static final Logger logger = LogManager.getLogger();
private static final String algorithm = "pbkdf2_sha256";
public String getEncodedHash(String password, String salt, int iterations) {
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(password.getBytes(StandardCharsets.UTF_8), salt.getBytes(), iterations);
byte[] dk = ((KeyParameter) generator.generateDerivedParameters(256)).getKey();
byte[] hashBase64 = Base64.getEncoder().encode(dk);
return new String(hashBase64);
}
public String encode(String password, String salt, int iterations) {
String hash = getEncodedHash(password, salt, iterations);
return String.format("%s$%d$%s$%s", algorithm, iterations, salt, hash);
}
@Override
public boolean check(String encryptedPassword, String password) {
String[] params = encryptedPassword.split("\\$");
if (params.length != 4) {
logger.warn(" end 1 " + params.length);
return false;
}
int iterations = Integer.parseInt(params[1]);
String salt = params[2];
String hash = encode(password, salt, iterations);
return hash.equals(encryptedPassword);
}
}

View file

@ -15,6 +15,7 @@ public static void registerProviders() {
providers.register("bcrypt", BCryptPasswordVerifier.class);
providers.register("accept", AcceptPasswordVerifier.class);
providers.register("reject", RejectPasswordVerifier.class);
providers.register("django", DjangoPasswordVerifier.class);
registeredProviders = true;
}
}

View file

@ -1,108 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
public class LocalProfileProvider extends ProfileProvider {
public String profilesDir = "profiles";
private transient volatile Map<Path, ClientProfile> profilesMap;
private transient volatile Set<ClientProfile> profilesList; // Cache
@Override
public void sync() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
Map<Path, ClientProfile> newProfiles = new HashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
Set<ClientProfile> newProfilesList = new HashSet<>(newProfiles.values());
profilesMap = newProfiles;
profilesList = newProfilesList;
}
@Override
public Set<ClientProfile> getProfiles() {
return profilesList;
}
@Override
public void addProfile(ClientProfile profile) throws IOException {
Path profilesDirPath = Path.of(profilesDir);
ClientProfile oldProfile;
Path target = null;
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
target = e.getKey();
}
}
if(target == null) {
target = profilesDirPath.resolve(profile.getTitle()+".json");
oldProfile = profilesMap.get(target);
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
throw new FileAlreadyExistsException(target.toString());
}
}
try (BufferedWriter writer = IOHelper.newWriter(target)) {
Launcher.gsonManager.configGson.toJson(profile, writer);
}
addProfile(target, profile);
}
@Override
public void deleteProfile(ClientProfile profile) throws IOException {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
Files.deleteIfExists(e.getKey());
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
}
private void addProfile(Path path, ClientProfile profile) {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
profilesMap.put(path, profile);
profilesList.add(profile);
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<Path, ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<Path, ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.put(file.toAbsolutePath(), profile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -0,0 +1,464 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
public class LocalProfilesProvider extends ProfilesProvider implements Reconfigurable {
private final transient Logger logger = LogManager.getLogger();
public String profilesDir = "profiles";
public String cacheFile = ".updates-cache";
public String updatesDir = "updates";
public boolean cacheUpdates = true;
private transient volatile Map<String, HashedDir> updatesDirMap;
private transient volatile Map<UUID, LocalProfile> profilesMap;
@Override
public UncompletedProfile create(String name, String description, CompletedProfile reference) {
LocalProfile ref = (LocalProfile) reference;
LocalProfile profile;
if(ref != null) {
ClientProfile newClientProfile = new ClientProfileBuilder(ref.profile)
.setTitle(name)
.setInfo(description)
.setDir(name)
.createClientProfile();
profile = new LocalProfile(newClientProfile, ref.clientDir, ref.assetDir);
Path updatesDirPath = Path.of(updatesDir);
try {
IOHelper.copy(updatesDirPath.resolve(ref.profile.getDir()), updatesDirPath.resolve(profile.profile.getDir()));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
profile = new LocalProfile(new ClientProfileBuilder()
.setUuid(UUID.randomUUID())
.setTitle(name)
.setInfo(description)
.setDir(name)
.setAssetDir("assets")
.createClientProfile(), null, getUpdatesDir("assets"));
}
pushProfileAndSave(profile);
return profile;
}
@Override
public void delete(UncompletedProfile profile) {
LocalProfile p = (LocalProfile) profile;
profilesMap.remove(p.getUuid());
try {
Path updatesDirPath = Path.of(updatesDir);
IOHelper.deleteDir(updatesDirPath.resolve(p.profile.getDir()), true);
Files.delete(p.getConfigPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Set<UncompletedProfile> getProfiles(Client client) {
if(client == null) {
return new HashSet<>(profilesMap.values());
}
if(!client.isAuth) {
return new HashSet<>();
}
Set<UncompletedProfile> profiles = new HashSet<>();
for(var p : profilesMap.entrySet()) {
var uuid = p.getKey();
var profile = p.getValue();
if(profile.getProfile() != null && profile.getProfile().isLimited()) {
if(client.isAuth && client.permissions != null && client.permissions.hasPerm(String.format("launchserver.profile.%s.show", uuid))) {
profiles.add(profile);
}
} else {
profiles.add(profile);
}
}
return profiles;
}
@Override
public CompletedProfile pushUpdate(UncompletedProfile profile, String tag, ClientProfile clientProfile, List<ProfileAction> assetActions, List<ProfileAction> clientActions, List<UpdateFlag> flags) throws IOException {
Path updatesDirPath = Path.of(updatesDir);
LocalProfile localProfile = (LocalProfile) profile;
localProfile = new LocalProfile(clientProfile, localProfile.clientDir, localProfile.assetDir);
localProfile.profile = clientProfile;
if(flags.contains(UpdateFlag.USE_DEFAULT_ASSETS)) {
if(getUpdatesDir("assets") == null) {
Path assetDirPath = updatesDirPath.resolve("assets");
if(!Files.exists(assetDirPath)) {
Files.createDirectories(assetDirPath);
}
var assetsHDir = new HashedDir(assetDirPath, null, true, true);
updatesDirMap.put("assets", assetsHDir);
localProfile.assetDir = assetsHDir;
}
}
if(assetActions != null && !assetActions.isEmpty()) {
Path assetDir = updatesDirPath.resolve(clientProfile.getAssetDir());
execute(localProfile.assetDir, assetDir, assetActions);
localProfile.assetDir = new HashedDir(assetDir, null, true, true);
}
if(clientActions != null && !clientActions.isEmpty()) {
Path clientDir = updatesDirPath.resolve(clientProfile.getDir());
execute(localProfile.clientDir, clientDir, clientActions);
localProfile.clientDir = new HashedDir(clientDir, null, true, true);
}
pushProfileAndSave(localProfile);
return localProfile;
}
private void pushProfileAndSave(LocalProfile localProfile) {
profilesMap.put(localProfile.getUuid(), localProfile);
try(Writer writer = IOHelper.newWriter(localProfile.getConfigPath())) {
Launcher.gsonManager.configGson.toJson(localProfile.profile, writer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void download(CompletedProfile profile, Map<String, Path> files, boolean assets) throws IOException {
Path sourceDir = Path.of(updatesDir).resolve(assets ? profile.getProfile().getAssetDir() : profile.getProfile().getDir());
for(var e : files.entrySet()) {
var source = sourceDir.resolve(e.getKey());
var target = e.getValue();
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
}
@Override
public HashedDir getUnconnectedDirectory(String name) {
return getUpdatesDir(name);
}
@Override
public CompletedProfile get(UUID uuid, String tag) {
return profilesMap.get(uuid);
}
@Override
public CompletedProfile get(String name, String tag) {
for(var p : profilesMap.values()) {
if(p.getName() != null && p.getName().equals(name)) {
return p;
}
}
return null;
}
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new ConcurrentHashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = updatesDirMap;
}
public void readProfilesDir() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
Map<UUID, LocalProfile> newProfiles = new ConcurrentHashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
profilesMap = newProfiles;
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new ConcurrentHashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = newUpdatesDirMap;
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
public HashedDir getUpdatesDir(String updateName) {
if(updateName == null) {
return null;
}
return updatesDirMap.get(updateName);
}
@Override
public void init(LaunchServer server) {
super.init(server);
if(server.env == LaunchServer.LaunchServerEnv.TEST) {
return;
}
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
readUpdatesDir();
} catch (IOException e) {
logger.error("Updates not synced", e);
}
try {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
readProfilesDir();
} catch (IOException e) {
logger.error("Profiles not synced", e);
}
}
public static void execute(HashedDir dir, Path updatesDirPath, List<ProfileAction> actions) throws IOException {
for(var action : actions) {
execute(dir, updatesDirPath, action);
}
}
public static void execute(HashedDir dir, Path updatesDirPath, ProfileAction action) throws IOException {
switch (action.type()) {
case UPLOAD -> {
Path target = updatesDirPath.resolve(action.target());
if(action.source() == null) {
IOHelper.createParentDirs(target);
IOHelper.transfer(action.input().get(), target);
} else {
Path source = Path.of(action.source());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
if(action.deleteSource()) {
IOHelper.createParentDirs(target);
IOHelper.move(source, target);
} else {
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
}
}
case COPY -> {
Path source = updatesDirPath.resolve(action.source());
Path target = updatesDirPath.resolve(action.target());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
case MOVE -> {
Path source = updatesDirPath.resolve(action.source());
Path target = updatesDirPath.resolve(action.target());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
IOHelper.createParentDirs(target);
IOHelper.move(source, target);
}
case DELETE -> {
Path target = updatesDirPath.resolve(action.target());
if(Files.isDirectory(target)) {
IOHelper.deleteDir(target, true);
}
}
}
}
@Override
public Map<String, Command> getCommands() {
return Map.of( "sync",
new SubCommand("[]", "sync all") {
@Override
public void invoke(String... args) throws Exception {
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
sync(null);
} catch (IOException e) {
logger.error("Updates not synced", e);
}
try {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
readProfilesDir();
} catch (IOException e) {
logger.error("Profiles not synced", e);
}
logger.info("Profiles and updates synced");
}
}
);
}
public class LocalProfile implements CompletedProfile {
private volatile ClientProfile profile;
private volatile HashedDir clientDir;
private volatile HashedDir assetDir;
private Path configPath;
public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir) {
this.profile = profile;
this.clientDir = clientDir;
this.assetDir = assetDir;
this.configPath = Path.of(profilesDir).resolve(profile.getDir().concat(".json"));
}
public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir, Path configPath) {
this.profile = profile;
this.clientDir = clientDir;
this.assetDir = assetDir;
this.configPath = configPath;
}
@Override
public String getTag() {
return null;
}
@Override
public ClientProfile getProfile() {
return profile;
}
@Override
public HashedDir getClientDir() {
return clientDir;
}
@Override
public HashedDir getAssetDir() {
return assetDir;
}
@Override
public UUID getUuid() {
return profile.getUUID();
}
@Override
public String getName() {
return profile.getTitle();
}
@Override
public String getDescription() {
return profile.getInfo();
}
@Override
public String getDefaultTag() {
return null;
}
public Path getConfigPath() {
return configPath;
}
}
private final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<UUID, LocalProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<UUID, LocalProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
LocalProfile localProfile = new LocalProfile(profile, getUpdatesDir(profile.getDir()), getUpdatesDir(profile.getAssetDir()), file);
result.put(localProfile.getUuid(), localProfile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -1,76 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class ProfileProvider {
public static final ProviderMap<ProfileProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfileProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract void sync() throws IOException;
public abstract Set<ClientProfile> getProfiles();
public abstract void addProfile(ClientProfile profile) throws IOException;
public abstract void deleteProfile(ClientProfile profile) throws IOException;
public void close() {
}
public ClientProfile getProfile(UUID uuid) {
for(var e : getProfiles()) {
if(e.getUUID().equals(uuid)) {
return e;
}
}
return null;
}
public ClientProfile getProfile(String title) {
for(var e : getProfiles()) {
if(e.getTitle().equals(title)) {
return e;
}
}
return null;
}
public List<ClientProfile> getProfiles(Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
profileList = new ArrayList<>(4);
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
} else {
profileList = List.copyOf(serverProfiles);
}
return profileList;
}
}

View file

@ -0,0 +1,87 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public abstract class ProfilesProvider {
public static final ProviderMap<ProfilesProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfilesProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract UncompletedProfile create(String name, String description, CompletedProfile basic);
public abstract void delete(UncompletedProfile profile);
public abstract Set<UncompletedProfile> getProfiles(Client client);
public abstract CompletedProfile pushUpdate(UncompletedProfile profile,
String tag,
ClientProfile clientProfile,
List<ProfileAction> assetActions,
List<ProfileAction> clientActions,
List<UpdateFlag> flags) throws IOException;
public abstract void download(CompletedProfile profile, Map<String, Path> files, boolean assets) throws IOException;
public abstract HashedDir getUnconnectedDirectory(String name);
public abstract CompletedProfile get(UUID uuid, String tag);
public abstract CompletedProfile get(String name, String tag);
public CompletedProfile get(UncompletedProfile profile) {
if(profile == null) {
return null;
}
return get(profile.getUuid(), null);
}
public void close() {
}
public interface UncompletedProfile {
UUID getUuid();
String getName();
String getDescription();
String getDefaultTag();
}
public interface CompletedProfile extends UncompletedProfile {
String getTag();
ClientProfile getProfile();
HashedDir getClientDir();
HashedDir getAssetDir();
}
public record ProfileAction(ProfileActionType type, String source, String target, Supplier<InputStream> input, Consumer<OutputStream> output, boolean deleteSource) {
public enum ProfileActionType {
UPLOAD, COPY, MOVE, DELETE
}
public static ProfileAction upload(Path source, String target, boolean deleteSource) {
return new ProfileAction(ProfileActionType.UPLOAD, source.toString(), target, null, null, deleteSource);
}
public static ProfileAction upload(Supplier<InputStream> input, String target) {
return new ProfileAction(ProfileActionType.UPLOAD, null, target, input, null, false);
}
}
public enum UpdateFlag {
USE_DEFAULT_ASSETS, DELETE_SOURCE_FILES
}
}

View file

@ -2,18 +2,11 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.util.*;
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
public class StdProtectHandler extends ProtectHandler {
private transient final Logger logger = LogManager.getLogger();
public Map<String, List<String>> profileWhitelist = new HashMap<>();
public List<String> allowUpdates = new ArrayList<>();
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@ -22,38 +15,6 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@Override
public void init(LaunchServer server) {
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
}
}
@Override
public boolean canGetProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
}
@Override
public boolean canChangeProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
}
@Override
public boolean canGetUpdates(String updatesDirName, Client client) {
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
}
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if (client.permissions != null) {
String permByUUID = property.formatted(profile.getUUID());
if (client.permissions.hasPerm(permByUUID)) {
return true;
}
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
if (client.permissions.hasPerm(permByTitle)) {
return true;
}
}
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
return allowedUsername != null && allowedUsername.contains(client.username);
}
}

View file

@ -1,22 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler {
default boolean canGetProfiles(Client client) {
return true;
}
default boolean canGetProfile(ClientProfile profile, Client client) {
return true;
}
default boolean canChangeProfile(ClientProfile profile, Client client) {
return client.isAuth;
}
default boolean canGetUpdates(String updatesDirName, Client client) {
return true;
}
}

View file

@ -2,185 +2,113 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.base.config.JsonConfigurable;
import pro.gravit.launcher.base.config.SimpleConfigurable;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Stream;
public class LocalUpdatesProvider extends UpdatesProvider {
private final transient Logger logger = LogManager.getLogger();
public String cacheFile = ".updates-cache";
private transient final Logger logger = LogManager.getLogger();
public String updatesDir = "updates";
public boolean cacheUpdates = true;
private volatile transient Map<String, HashedDir> updatesDirMap;
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
public void readUpdatesFromCache() throws IOException {
readCache(Path.of(cacheFile));
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
public String binaryName = "Launcher";
public String buildSecretsFile = "build-secrets.json";
public Map<UpdateVariant, String> urls = new HashMap<>(Map.of(
UpdateVariant.JAR, "http://localhost:9274/Launcher.jar",
UpdateVariant.EXE, "http://localhost:9274/Launcher.exe"
));
public transient JsonConfigurable<BuildSecretsInfo> buildSecretsJson;
private final transient Map<UpdateVariant, byte[]> hashMap = new HashMap<>();
@Override
public void init(LaunchServer server) {
super.init(server);
buildSecretsJson = new SimpleConfigurable<>(BuildSecretsInfo.class, Path.of(buildSecretsFile));
if(server.env == LaunchServer.LaunchServerEnv.TEST) {
return;
}
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
buildSecretsJson.generateConfigIfNotExists();
buildSecretsJson.loadConfig();
} catch (Exception e) {
buildSecretsJson.setConfig(buildSecretsJson.getDefaultConfig());
}
try {
sync(UpdateVariant.JAR);
sync(UpdateVariant.EXE);
} catch (IOException e) {
logger.error("Updates not synced", e);
logger.error("Error when syncing binaries", e);
}
}
@Override
public void syncInitially() throws IOException {
readUpdatesDir();
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
@Override
public HashedDir getUpdatesDir(String updateName) {
return updatesDirMap.get(updateName);
}
private Path resolveUpdateName(String updateName) {
if(updateName == null) {
return Path.of(updatesDir);
}
return Path.of(updatesDir).resolve(updateName);
}
@Override
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) {
var target = path.resolve(e.getKey());
var source = e.getValue();
IOHelper.createParentDirs(target);
if(deleteAfterUpload) {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
} else {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@Override
public Map<String, Path> download(String updateName, List<String> files) {
var path = resolveUpdateName(updateName);
Map<String, Path> map = new HashMap<>();
public void pushUpdate(List<UpdateUploadInfo> files) throws IOException {
for(var e : files) {
map.put(e, path.resolve(e));
IOHelper.copy(e.path(), getUpdate(e.variant()));
buildSecretsJson.getConfig().secrets().put(e.variant(), e.secrets());
sync(e.variant());
}
return map;
buildSecretsJson.saveConfig();
}
public void sync(UpdateVariant variant) throws IOException {
var source = getUpdate(variant);
if(!Files.exists(source)) {
logger.warn("Dont exist {} binary", variant);
return;
}
byte[] hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, source);
hashMap.put(variant, hash);
}
public Path getUpdate(UpdateVariant variant) {
String fileName;
switch (variant) {
case JAR -> {
fileName = binaryName.concat(".jar");
}
case EXE -> {
fileName = binaryName.concat(".exe");
}
default -> {
fileName = binaryName;
}
}
return Path.of(updatesDir).resolve(fileName);
}
@Override
public void delete(String updateName, List<String> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files) {
var target = path.resolve(e);
Files.delete(target);
public UpdateInfo checkUpdates(UpdateVariant variant, BuildSecretsCheck buildSecretsCheck) {
byte[] hash = hashMap.get(variant);
if (hash == null) {
return null; // We dont have this file
}
if(checkSecureHash(buildSecretsCheck.secureHash(), buildSecretsCheck.secureSalt(), buildSecretsJson.getConfig().secrets().get(variant).secureToken()) && Arrays.equals(buildSecretsCheck.digest(), hash)) {
return null; // Launcher already updated
}
return new UpdateInfo(urls.get(variant));
}
@Override
public void delete(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
IOHelper.deleteDir(path, true);
public static final class BuildSecretsInfo {
private Map<UpdateVariant, BuildSecrets> secrets = new HashMap<>();
public BuildSecretsInfo(Map<UpdateVariant, BuildSecrets> secrets) {
this.secrets = secrets;
}
@Override
public void create(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
Files.createDirectories(path);
public BuildSecretsInfo() {
}
public Map<UpdateVariant, BuildSecrets> secrets() {
return secrets;
}
}
}

View file

@ -1,16 +1,19 @@
package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public abstract class UpdatesProvider {
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
@ -26,27 +29,37 @@ public void init(LaunchServer server) {
this.server = server;
}
public void sync() throws IOException {
sync(null);
public abstract void pushUpdate(List<UpdateUploadInfo> files) throws IOException;
public abstract UpdateInfo checkUpdates(UpdateVariant variant, BuildSecretsCheck buildSecretsCheck);
protected boolean checkSecureHash(String secureHash, String secureSalt, String privateSecureToken) {
if (secureHash == null || secureSalt == null) return false;
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
privateSecureToken.concat(".").concat(secureSalt));
byte[] launcher_hash = Base64.getDecoder().decode(secureHash);
return Arrays.equals(normal_hash, launcher_hash);
}
public abstract void syncInitially() throws IOException;
public abstract void sync(Collection<String> updateNames) throws IOException;
public abstract HashedDir getUpdatesDir(String updateName);
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
public abstract Map<String, Path> download(String updateName, List<String> files);
public abstract void delete(String updateName, List<String> files) throws IOException;
public abstract void delete(String updateName) throws IOException;
public abstract void create(String updateName) throws IOException;
public void close() {
}
public enum UpdateVariant {
JAR, EXE
}
public record UpdateInfo(String url) {
}
public record UpdateUploadInfo(Path path, UpdateVariant variant, BuildSecrets secrets) {
}
public record BuildSecrets(String secureToken, byte[] digest) {
}
public record BuildSecretsCheck(String secureHash, String secureSalt, byte[] digest) {
}
}

View file

@ -14,15 +14,8 @@
public abstract class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final Path buildDir;
public final String nameFormat;
protected transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir;
this.nameFormat = nameFormat;
}
public void addCounted(int count, Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
List<LauncherBuildTask> indexes = new ArrayList<>();
tasks.stream().filter(pred).forEach(indexes::add);
@ -77,20 +70,4 @@ public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pr
}
return Optional.empty();
}
public String nextName(String taskName) {
return nameFormat.formatted(taskName);
}
public Path nextPath(String taskName) {
return buildDir.resolve(nextName(taskName));
}
public Path nextPath(LauncherBuildTask task) {
return nextPath(task.getName());
}
public Path nextLowerPath(LauncherBuildTask task) {
return nextPath(CommonHelper.low(task.getName()));
}
}

View file

@ -5,6 +5,7 @@
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.launchserver.asm.InjectClassAcceptor;
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -27,9 +28,7 @@
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
@ -40,16 +39,20 @@
import static pro.gravit.utils.helper.IOHelper.newZipEntry;
public class BuildContext {
public final PipelineContext pipelineContext;
public final ZipOutputStream output;
public final List<JarFile> readerClassPath;
public final MainBuildTask task;
public final HashSet<String> fileList;
public final HashSet<String> clientModules;
public final HashSet<String> legacyClientModules;
public final Map<String, Object> properties;
public final List<MainBuildTask.Transformer> transformers = new ArrayList<>();
private Path runtimeDir;
private boolean deleteRuntimeDir;
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
public BuildContext(PipelineContext pipelineContext, ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
this.pipelineContext = pipelineContext;
this.output = output;
this.readerClassPath = readerClassPath;
this.task = task;
@ -57,6 +60,9 @@ public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainB
fileList = new HashSet<>(1024);
clientModules = new HashSet<>();
legacyClientModules = new HashSet<>();
properties = new HashMap<>();
InjectClassAcceptor injectClassAcceptor = new InjectClassAcceptor(properties);
transformers.add(injectClassAcceptor);
}
public void pushFile(String filename, InputStream inputStream) throws IOException {

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
@ -9,14 +10,17 @@
public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe");
super(server);
}
@Override
public void build() throws IOException {
if (IOHelper.isFile(syncBinaryFile)) {
Files.delete(syncBinaryFile);
public UpdatesProvider.UpdateVariant getVariant() {
return UpdatesProvider.UpdateVariant.EXE;
}
@Override
public PipelineContext build() throws IOException {
return new PipelineContext(server);
}
}

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.*;
import java.io.IOException;
@ -23,7 +24,7 @@ public final class JARLauncherBinary extends LauncherBinary {
public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s.jar");
super(server);
count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
buildDir = server.dir.resolve("build");
@ -36,6 +37,11 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
}
}
@Override
public UpdatesProvider.UpdateVariant getVariant() {
return UpdatesProvider.UpdateVariant.JAR;
}
@Override
public void init() {
tasks.add(new PrepareBuildTask(server));

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -12,58 +13,40 @@
public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server;
public final Path syncBinaryFile;
private volatile byte[] digest;
public final PipelineContext context;
protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) {
super(server.tmpDir.resolve("build"), nameFormat);
protected LauncherBinary(LaunchServer server) {
this.server = server;
syncBinaryFile = binaryFile;
this.context = new PipelineContext(server);
}
public static Path resolve(LaunchServer server, String ext) {
return Path.of(server.config.binaryName + ext);
}
public void build() throws IOException {
public PipelineContext build() throws IOException {
logger.info("Building launcher binary file");
Path thisPath = null;
long time_start = System.currentTimeMillis();
long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
Path newPath = task.process(context);
if(newPath != null) {
context.setLastest(newPath);
context.putArtifact(task.getName(), newPath);
}
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
logger.info("Task {} processed from {} millis", task.getName(), time_task);
}
long time_end = System.currentTimeMillis();
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
return this.context;
}
public final boolean exists() {
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
}
public final byte[] getDigest() {
return digest;
}
public abstract UpdatesProvider.UpdateVariant getVariant();
public void init() {
}
public final boolean sync() {
try {
var target = syncBinaryFile.toString();
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
return true;
} catch (Throwable e) {
return false;
}
}
}

View file

@ -0,0 +1,94 @@
package pro.gravit.launchserver.binary;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class PipelineContext {
private final Logger logger = LogManager.getLogger();
private final LaunchServer launchServer;
private final Map<String, Path> artifacts = new HashMap<>();
private final Map<String, Object> properties = new HashMap<>();
private Set<Path> tempFiles = new HashSet<>();
private Path lastest;
public PipelineContext(LaunchServer launchServer) {
this.launchServer = launchServer;
}
public Path makeTempPath(String name, String ext) throws IOException {
return this.launchServer.createTempFilePath(name, ext);
}
public Map<String, Path> getArtifacts() {
return artifacts;
}
public Path getLastest() {
return lastest;
}
public void setLastest(Path lastest) {
this.lastest = lastest;
}
public void putArtifact(String name, Path path) {
artifacts.put(name, path);
}
public void putProperty(String name, Object prop) {
properties.put(name, prop);
}
public void putProperties(Map<String, Object> properties) {
this.properties.putAll(properties);
}
public LaunchServer getLaunchServer() {
return launchServer;
}
@SuppressWarnings("unchecked")
public<T> T getProperty(String name) {
return (T) properties.get(name);
}
public UpdatesProvider.UpdateUploadInfo makeUploadInfo(UpdatesProvider.UpdateVariant variant) {
if(getLastest() == null) {
return null;
}
try {
byte[] hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, getLastest());
return new UpdatesProvider.UpdateUploadInfo(getLastest(), variant, new UpdatesProvider.BuildSecrets(
getProperty("checkClientSecret"), hash
));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void clear() {
for(var p : tempFiles) {
if(Files.exists(p)) {
try {
Files.delete(p);
} catch (IOException e) {
logger.error("Couldn't delete file {}", p, e);
}
}
}
tempFiles.clear();
properties.clear();
artifacts.clear();
}
}

View file

@ -6,6 +6,7 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.SafeClassWriter;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
@ -66,8 +67,9 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
Path out = server.launcherBinary.nextPath("post-fixed");
public Path process(PipelineContext context) throws IOException {
Path inputFile = context.getLastest();
Path out = context.makeTempPath("post-fixed", ".jar");
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
apply(inputFile, inputFile, output, server, (e) -> false, true);
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary.tasks;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
@ -32,8 +33,9 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
Path outputFile = srv.launcherBinary.nextPath("attached");
public Path process(PipelineContext context) throws IOException {
Path inputFile = context.getLastest();
Path outputFile = context.makeTempPath("attached", ".jar");
try (ZipInputStream input = IOHelper.newZipInput(inputFile);
ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputFile))) {
ZipEntry e = input.getNextEntry();

View file

@ -18,6 +18,7 @@
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.helper.SignHelper;
import java.io.IOException;
@ -49,8 +50,8 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
if (signedDataGenerator != null) return inputFile;
public Path process(PipelineContext context) throws IOException {
if (signedDataGenerator != null) return null;
try {
logger.warn("You are using an auto-generated certificate (sign.enabled false). It is not good");
logger.warn("It is highly recommended that you use the correct certificate (sign.enabled true)");
@ -79,6 +80,6 @@ public Path process(Path inputFile) throws IOException {
} catch (OperatorCreationException | CMSException | CertificateException e) {
logger.error("Certificate generate failed", e);
}
return inputFile;
return null;
}
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary.tasks;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
@ -23,8 +24,9 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
Path output = server.launcherBinary.nextPath(this);
public Path process(PipelineContext context) throws IOException {
Path inputFile = context.getLastest();
Path output = context.makeTempPath("compress", ".jar");
try (ZipOutputStream outputStream = new ZipOutputStream(IOHelper.newOutput(output))) {
outputStream.setMethod(ZipOutputStream.DEFLATED);
outputStream.setLevel(Deflater.BEST_COMPRESSION);

View file

@ -1,10 +1,12 @@
package pro.gravit.launchserver.binary.tasks;
import pro.gravit.launchserver.binary.PipelineContext;
import java.io.IOException;
import java.nio.file.Path;
public interface LauncherBuildTask {
String getName();
Path process(Path inputFile) throws IOException;
Path process(PipelineContext context) throws IOException;
}

View file

@ -15,6 +15,7 @@
import pro.gravit.launchserver.asm.InjectClassAcceptor;
import pro.gravit.launchserver.asm.SafeClassWriter;
import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.HookException;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -30,18 +31,14 @@
public class MainBuildTask implements LauncherBuildTask {
public final ClassMetadataReader reader;
public final Set<String> blacklist = new HashSet<>();
public final List<Transformer> transformers = new ArrayList<>();
public final IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
public final IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
public final Map<String, Object> properties = new HashMap<>();
private final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
public MainBuildTask(LaunchServer srv) {
server = srv;
reader = new ClassMetadataReader();
InjectClassAcceptor injectClassAcceptor = new InjectClassAcceptor(properties);
transformers.add(injectClassAcceptor);
}
@Override
@ -50,15 +47,16 @@ public String getName() {
}
@Override
public Path process(Path inputJar) throws IOException {
Path outputJar = server.launcherBinary.nextPath(this);
public Path process(PipelineContext pipelineContext) throws IOException {
Path inputJar = pipelineContext.getLastest();
Path outputJar = pipelineContext.makeTempPath("main", ".jar");
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir);
initProps();
BuildContext context = new BuildContext(pipelineContext, output, reader.getCp(), this, server.launcherBinary.runtimeDir);
initProps(context);
preBuildHook.hook(context);
properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
postInitProps();
context.properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
context.properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
postInitProps(context);
reader.getCp().add(new JarFile(inputJar.toFile()));
for (Path e : server.launcherBinary.coreLibs) {
reader.getCp().add(new JarFile(e.toFile()));
@ -69,7 +67,8 @@ public Path process(Path inputJar) throws IOException {
Map<String, byte[]> runtime = new HashMap<>(256);
// Write launcher guard dir
if (server.config.launcher.encryptRuntime) {
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
String runtimeEncryptKey = context.pipelineContext.getProperty("runtimeEncryptKey");
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtimeEncryptKey, runtime, false);
} else {
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false);
}
@ -85,7 +84,7 @@ public Path process(Path inputJar) throws IOException {
return outputJar;
}
protected void postInitProps() {
protected void postInitProps(BuildContext context) {
List<byte[]> certificates = Arrays.stream(server.certificateManager.trustManager.getTrusted()).map(e -> {
try {
return e.getEncoded();
@ -102,41 +101,41 @@ protected void postInitProps() {
throw new InternalError(e);
}
}
properties.put("launchercore.certificates", certificates);
context.properties.put("launchercore.certificates", certificates);
}
protected void initProps() {
properties.clear();
properties.put("launcher.address", server.config.netty.address);
properties.put("launcher.projectName", server.config.projectName);
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
properties.put("launchercore.env", server.config.env);
properties.put("launcher.memory", server.config.launcher.memoryLimit);
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
protected void initProps(BuildContext context) {
context.properties.clear();
context.properties.put("launcher.address", server.config.netty.address);
context.properties.put("launcher.projectName", server.config.projectName);
context.properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
context.properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
context.properties.put("launchercore.env", server.config.env);
context.properties.put("launcher.memory", server.config.launcher.memoryLimit);
context.properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
if (server.config.launcher.encryptRuntime) {
if (server.runtime.runtimeEncryptKey == null)
server.runtime.runtimeEncryptKey = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.runtimeEncryptKey", server.runtime.runtimeEncryptKey);
String runtimeEncryptKey = SecurityHelper.randomStringToken();
context.pipelineContext.putProperty("runtimeEncryptKey", runtimeEncryptKey);
context.properties.put("runtimeconfig.runtimeEncryptKey", runtimeEncryptKey);
}
properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey);
context.properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
String checkClientSecret = SecurityHelper.randomStringToken();
context.pipelineContext.putProperty("checkClientSecret", checkClientSecret);
String launcherSalt = SecurityHelper.randomStringToken();
byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
server.runtime.buildNumber++;
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
checkClientSecret.concat(".").concat(launcherSalt));
context.properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
context.properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
String unlockSecret = SecurityHelper.randomStringToken();
context.pipelineContext.putProperty("unlockSecret", unlockSecret);
context.properties.put("runtimeconfig.unlockSecret", unlockSecret);
}
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
byte[] result = bytes;
ClassWriter writer;
ClassNode cn = null;
for (Transformer t : transformers) {
for (Transformer t : context.transformers) {
if (t instanceof ASMTransformer asmTransformer) {
if (cn == null) {
ClassReader cr = new ClassReader(result);

View file

@ -3,6 +3,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.UnpackHelper;
@ -29,7 +30,7 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
public Path process(PipelineContext context) throws IOException {
server.launcherBinary.coreLibs.clear();
server.launcherBinary.addonLibs.clear();
server.launcherBinary.files.clear();

View file

@ -6,6 +6,7 @@
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.operator.OperatorCreationException;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.binary.SignerJar;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.helper.SignHelper;
@ -52,8 +53,9 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
Path toRet = srv.launcherBinary.nextPath("signed");
public Path process(PipelineContext context) throws IOException {
Path inputFile = context.getLastest();
Path toRet = context.makeTempPath("signed", ".jar");
sign(config, inputFile, toRet);
return toRet;
}

View file

@ -44,7 +44,7 @@ protected boolean showApplyDialog(String text) throws IOException {
return response.equals("y");
}
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
public static Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
long total = 0;
for (Downloader.SizedFile file : list) {
if(file.size < 0) {

View file

@ -21,6 +21,5 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
server.buildLauncherBinaries();
server.syncLauncherBinaries();
}
}

View file

@ -33,10 +33,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
// Register sync commands
BaseCommandCategory updates = new BaseCommandCategory();
updates.registerCommand("indexAsset", new IndexAssetCommand(server));
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
//updates.registerCommand("indexAsset", new IndexAssetCommand(server));
//updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("sync", new SyncCommand(server));
updates.registerCommand("profile", new ProfilesCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");

View file

@ -1,135 +0,0 @@
package pro.gravit.launchserver.command.hash;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class DownloadAssetCommand extends Command {
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
private transient final Logger logger = LogManager.getLogger();
public DownloadAssetCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mojang/mirror)";
}
@Override
public String getUsageDescription() {
return "Download asset dir";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets");
String type = args.length > 2 ? args[2] : "mojang";
Path assetDir = server.updatesDir.resolve(dirName);
// Create asset dir
if (Files.notExists(assetDir)) {
logger.info("Creating asset dir: '{}'", dirName);
Files.createDirectory(assetDir);
}
if (type.equals("mojang")) {
HttpRequester requester = new HttpRequester();
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL);
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null;
for (var e : versions.versions) {
if (e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
if (profileUrl == null) {
logger.error("Version {} not found", versionName);
return;
}
logger.info("Fetch profile {} from {}", versionName, profileUrl);
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json");
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
JsonObject objects = assets.get("objects").getAsJsonObject();
try (Writer writer = IOHelper.newWriter(indexPath)) {
logger.info("Save {}", indexPath);
Launcher.gsonManager.configGson.toJson(assets, writer);
}
if (!assetIndex.equals(versionName)) {
Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json");
logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
var target = assetDir.resolve(path);
if (Files.exists(target)) {
long fileSize = Files.size(target);
if (fileSize != size) {
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
} else {
continue;
}
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
logger.info("Download {} files", toDownload.size());
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
} else {
// Download required asset
logger.info("Downloading asset, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
}
// Finished
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Asset successfully downloaded: '{}'", dirName);
}
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
}

View file

@ -1,106 +0,0 @@
package pro.gravit.launchserver.command.hash;
import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.ClientProfileVersions;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import pro.gravit.utils.command.CommandException;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.UUID;
public final class DownloadClientCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public DownloadClientCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mirror/generate)";
}
@Override
public String getUsageDescription() {
return "Download client dir";
}
@Override
public void invoke(String... args) throws IOException, CommandException {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
Path clientDir = server.updatesDir.resolve(dirName);
boolean isMirrorClientDownload = false;
if (args.length > 2) {
isMirrorClientDownload = args[2].equals("mirror");
}
// Download required client
logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName);
// Create profile file
logger.info("Creaing profile file: '{}'", dirName);
ClientProfile clientProfile = null;
if (isMirrorClientDownload) {
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorClientDownload = false;
}
}
if (!isMirrorClientDownload) {
try {
String internalVersion = versionName;
if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
}
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) {
isMirrorClientDownload = true;
}
}
server.config.profileProvider.addProfile(clientProfile);
// Finished
server.syncProfilesDir();
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Client successfully downloaded: '{}'", dirName);
}
}

View file

@ -19,6 +19,8 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class IndexAssetCommand extends Command {
public static final String INDEXES_DIR = "indexes";
@ -50,28 +52,25 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
/*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
if (outputAssetDir.equals(inputAssetDir))
throw new CommandException("Unindexed and indexed asset dirs can't be same");
// Create new asset dir
logger.info("Creating indexed asset dir: '{}'", outputAssetDirName);
Files.createDirectory(outputAssetDir);
Path inputAssetDir = Path.of(inputAssetDirName);
Map<String, Path> uploadMap = new HashMap<>();
// Index objects
JsonObject objects = new JsonObject();
logger.info("Indexing objects");
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, outputAssetDir), false);
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, uploadMap), false);
server.config.updatesProvider.upload(outputAssetDirName, uploadMap, false);
// Write index file
logger.info("Writing asset index file: '{}'", indexFileName);
try (BufferedWriter writer = IOHelper.newWriter(resolveIndexFile(outputAssetDir, indexFileName))) {
var indexFile = resolveIndexFile(Path.of(""), indexFileName);
try (BufferedWriter writer = IOHelper.newWriter(server.config.updatesProvider.upload(outputAssetDirName, indexFile.toString()))) {
JsonObject result = new JsonObject();
result.add("objects", objects);
writer.write(Launcher.gsonManager.gson.toJson(result));
@ -79,7 +78,7 @@ public void invoke(String... args) throws Exception {
// Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully indexed: '{}'", inputAssetDirName);
logger.info("Asset successfully indexed: '{}'", inputAssetDirName);*/
}
public static class IndexObject {
@ -95,12 +94,12 @@ public IndexObject(long size, String hash) {
private final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
private final JsonObject objects;
private final Path inputAssetDir;
private final Path outputAssetDir;
private final Map<String, Path> uploadMap;
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) {
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Map<String, Path> uploadMap) {
this.objects = objects;
this.inputAssetDir = inputAssetDir;
this.outputAssetDir = outputAssetDir;
this.uploadMap = uploadMap;
}
@Override
@ -112,7 +111,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
IndexObject obj = new IndexObject(attrs.size(), digest);
objects.add(name, Launcher.gsonManager.gson.toJsonTree(obj));
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest));
uploadMap.put(resolveObjectFile(Path.of(""), digest).toString(), file);
// Continue visiting
return super.visitFile(file, attrs);

View file

@ -5,6 +5,7 @@
import com.google.gson.JsonParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.CommandException;
@ -14,6 +15,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public final class UnindexAssetCommand extends Command {
@ -35,14 +37,15 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
/*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
if (outputAssetDir.equals(inputAssetDir))
throw new CommandException("Indexed and unindexed asset dirs can't be same");
var updatesDir = server.config.updatesProvider.getUpdatesDir(inputAssetDirName);
if(updatesDir == null) {
server.config.updatesProvider.create(inputAssetDirName);
}
Path outputAssetDir = Path.of(outputAssetDirName);
// Create new asset dir
logger.info("Creating unindexed asset dir: '{}'", outputAssetDirName);
@ -51,7 +54,8 @@ public void invoke(String... args) throws Exception {
// Read JSON file
JsonObject objects;
logger.info("Reading asset index file: '{}'", indexFileName);
try (BufferedReader reader = IOHelper.newReader(IndexAssetCommand.resolveIndexFile(inputAssetDir, indexFileName))) {
Path indexFilePath = IndexAssetCommand.resolveIndexFile(Path.of(""), indexFileName);
try (BufferedReader reader = IOHelper.newReader(server.config.updatesProvider.download(inputAssetDirName, indexFilePath.toString()))) {
objects = JsonParser.parseReader(reader).getAsJsonObject().get("objects").getAsJsonObject();
}
@ -63,12 +67,12 @@ public void invoke(String... args) throws Exception {
// Copy hashed file to target
String hash = member.getValue().getAsJsonObject().get("hash").getAsString();
Path source = IndexAssetCommand.resolveObjectFile(inputAssetDir, hash);
IOHelper.copy(source, outputAssetDir.resolve(name));
Path source = IndexAssetCommand.resolveObjectFile(Path.of(""), hash);
server.config.updatesProvider.download(inputAssetDirName, Map.of(source.toString(), outputAssetDir.resolve(name)));
}
// Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully unindexed: '{}'", inputAssetDirName);
logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString());*/
}
}

View file

@ -5,6 +5,7 @@
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
@ -34,36 +35,14 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
ClientProfile profile;
ProfilesProvider.CompletedProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
profile = server.config.profilesProvider.get(args[0], null);
}
var builder = new ClientProfileBuilder(profile);
builder.setTitle(args[1]);
builder.setUuid(UUID.randomUUID());
if(profile.getServers().size() == 1) {
profile.getServers().getFirst().name = args[1];
}
logger.info("Copy {} to {}", profile.getDir(), args[1]);
var src = server.updatesDir.resolve(profile.getDir());
var dest = server.updatesDir.resolve(args[1]);
try (Stream<Path> stream = Files.walk(src)) {
stream.forEach(source -> {
try {
IOHelper.copy(source, dest.resolve(src.relativize(source)));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
builder.setDir(args[1]);
profile = builder.createClientProfile();
server.config.profileProvider.addProfile(profile);
server.config.profilesProvider.create(args[1], "Description", profile);
logger.info("Profile {} cloned from {}", args[1], args[0]);
server.syncProfilesDir();
server.syncUpdatesDir(List.of(args[1]));
}
}

View file

@ -0,0 +1,140 @@
package pro.gravit.launchserver.command.profiles;
import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Downloader;
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.ClientProfileVersions;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.AssetsDirHelper;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class CreateProfileCommand extends Command {
private static final Logger logger = LogManager.getLogger();
public CreateProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mirror/generate)";
}
@Override
public String getUsageDescription() {
return "Download client";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = args[1];
Path clientDir = server.createTempDirectory("client");
boolean isMirrorProfileDownload = false;
if (args.length > 2) {
isMirrorProfileDownload = args[2].equals("mirror");
}
// Download required client
logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName);
// Create profile file
logger.info("Creaing profile file: '{}'", dirName);
ClientProfile clientProfile = null;
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorProfileDownload = false;
}
if (!isMirrorProfileDownload) {
try {
String internalVersion = versionName;
if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
}
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) {
isMirrorProfileDownload = true;
}
}
pushClientAndDownloadAssets(server, clientProfile, clientDir);
// Finished
logger.info("Client successfully downloaded: '{}'", dirName);
}
public static ProfilesProvider.CompletedProfile pushClientAndDownloadAssets(LaunchServer server, ClientProfile clientProfile, Path clientDir) throws Exception {
var uncompleted = server.config.profilesProvider.create(clientProfile.getTitle(), "Description", null);
var completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
{
String assetIndexPath = String.format("indexes/%s.json", completed.getProfile().getAssetIndex());
if (!completed.getAssetDir().tryFindRecursive(assetIndexPath).isFound()) {
Path assetDir = server.createTempDirectory("assets");
HttpRequester requester = new HttpRequester();
var assetInfo = AssetsDirHelper.getAssetInfo(requester, completed.getProfile().getAssetIndex());
var toDownload = AssetsDirHelper.makeToDownloadFiles(assetInfo, completed.getAssetDir());
logger.info("Download assets {}", completed.getProfile().getAssetIndex());
Downloader downloader = downloadWithProgressBar(completed.getProfile().getAssetIndex(),
toDownload, AssetsDirHelper.RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
byte[] assetIndexBytes = Launcher.gsonManager.configGson.toJson(assetInfo.assets()).getBytes(StandardCharsets.UTF_8);
completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, List.of(
ProfilesProvider.ProfileAction.upload(() -> {
return new ByteArrayInputStream(assetIndexBytes);
}, assetIndexPath),
ProfilesProvider.ProfileAction.upload(assetDir, "", true)
), List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
} else {
completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
}
}
return completed;
}
}

View file

@ -4,6 +4,7 @@
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command;
import java.util.UUID;
@ -27,24 +28,18 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
ClientProfile profile;
ProfilesProvider.CompletedProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
profile = server.config.profilesProvider.get(args[0], null);
}
if(profile == null) {
logger.error("Profile {} not found", args[0]);
return;
}
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir());
if(!showApplyDialog("Continue?")) {
return;
}
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
server.config.profileProvider.deleteProfile(profile);
logger.info("Delete {}", profile.getDir());
server.config.updatesProvider.delete(profile.getDir());
logger.info("Delete {}", args[0]);
server.config.profilesProvider.delete(profile);
}
}

View file

@ -23,8 +23,8 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) {
for(var profile : server.getProfiles()) {
logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : "");
for(var profile : server.config.profilesProvider.getProfiles(null)) {
logger.info("{} ({})", profile.getName(), profile.getUuid());
}
}
}

View file

@ -1,40 +0,0 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
public class MakeProfileCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public MakeProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[name] [minecraft version] [dir]";
}
@Override
public String getUsageDescription() {
return "make profile for any minecraft versions";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
ClientProfile.Version version = parseClientVersion(args[1]);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(server.updatesDir.resolve(args[2]), version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.info("Detected option {}", option);
}
ClientProfile profile = MakeProfileHelper.makeProfile(version, args[0], options);
server.config.profileProvider.addProfile(profile);
logger.info("Profile {} created", args[0]);
server.syncProfilesDir();
}
}

View file

@ -6,8 +6,7 @@
public class ProfilesCommand extends Command {
public ProfilesCommand(LaunchServer server) {
super(server);
this.childCommands.put("make", new MakeProfileCommand(server));
this.childCommands.put("save", new SaveProfilesCommand(server));
this.childCommands.put("create", new CreateProfileCommand(server));
this.childCommands.put("clone", new CloneProfileCommand(server));
this.childCommands.put("list", new ListProfilesCommand(server));
this.childCommands.put("delete", new DeleteProfileCommand(server));

View file

@ -1,46 +0,0 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.util.UUID;
public class SaveProfilesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SaveProfilesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[profile names...]";
}
@Override
public String getUsageDescription() {
return "load and save profile";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
if (args.length > 0) {
for (String profileName : args) {
ClientProfile profile;
try {
UUID uuid = UUID.fromString(profileName);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(profileName);
}
server.config.profileProvider.addProfile(profile);
}
server.syncProfilesDir();
}
}
}

View file

@ -149,8 +149,8 @@ public void invoke(String... args) {
case PROD -> printCheckResult("env", "", true);
}
//Profiles
for (ClientProfile profile : server.getProfiles()) {
//Profiles TODO: Implement
/*for (ClientProfile profile : server.config.profilesProvider.getProfiles(null)) {
boolean bad = false;
String profileModuleName = "profiles.%s".formatted(profile.getTitle());
for (String exc : profile.getUpdateExclusions()) {
@ -177,7 +177,7 @@ public void invoke(String... args) {
}
if (!bad)
printCheckResult(profileModuleName, "", true);
}
}*/
//Linux permissions check
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
@ -206,7 +206,7 @@ public void invoke(String... args) {
logger.warn("Write access to LaunchServer.jar. Please use 'chmod 755 LaunchServer.jar'");
}
if (Files.exists(server.dir.resolve(".keys")) && checkOtherReadOrWriteAccess(server.dir.resolve(".keys"))) {
logger.warn("Write or read access to .keys directory. Please use 'chmod -R 600 .keys'");
logger.warn("Write or read access to .keys directory. Please use 'chmod -r 700.keys");
}
if (Files.exists(server.dir.resolve("LaunchServerConfig.json")) && checkOtherReadOrWriteAccess(server.dir.resolve("LaunchServerConfig.json"))) {
logger.warn("Write or read access to LaunchServerConfig.json. Please use 'chmod 600 LaunchServerConfig.json'");

View file

@ -6,6 +6,7 @@
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.SubCommand;
@ -28,9 +29,9 @@ public void invoke(String... args) throws Exception {
public void invoke(String... args) {
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]);
ClientProfile profile = null;
for (ClientProfile p : server.getProfiles()) {
if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) {
ProfilesProvider.UncompletedProfile profile = null;
for (var p : server.config.profilesProvider.getProfiles(null)) {
if (p.getName().equals(args[0]) || p.getUuid().toString().equals(args[0])) {
profile = p;
break;
}
@ -42,7 +43,7 @@ public void invoke(String... args) {
logger.error("AuthId {} not found", args[1]);
return;
}
String token = server.authManager.newCheckServerToken(profile != null ? profile.getUUID().toString() : args[0], pair.name, publicOnly);
String token = server.authManager.newCheckServerToken(profile != null ? profile.getUuid().toString() : args[0], pair.name, publicOnly);
logger.info("Server token {} authId {}: {}", args[0], pair.name, token);
}
});

View file

@ -1,32 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncBinariesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncBinariesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync launcher binaries";
}
@Override
public void invoke(String... args) throws IOException {
server.syncLauncherBinaries();
logger.info("Binaries successfully resynced");
}
}

View file

@ -6,12 +6,7 @@
public class SyncCommand extends Command {
public SyncCommand(LaunchServer server) {
super(server);
this.childCommands.put("profiles", new SyncProfilesCommand(server));
this.childCommands.put("binaries", new SyncBinariesCommand(server));
this.childCommands.put("updates", new SyncUpdatesCommand(server));
this.childCommands.put("up", new SyncUPCommand(server));
this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server));
this.childCommands.put("updatescache", new SyncUpdatesCacheCommand(server));
}
@Override

View file

@ -1,32 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncProfilesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncProfilesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync profiles dir";
}
@Override
public void invoke(String... args) throws IOException {
server.syncProfilesDir();
logger.info("Profiles successfully resynced");
}
}

View file

@ -1,35 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncUPCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncUPCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync profiles & updates dirs";
}
@Override
public void invoke(String... args) throws IOException {
server.syncProfilesDir();
logger.info("Profiles successfully resynced");
server.syncUpdatesDir(null);
logger.info("Updates dir successfully resynced");
}
}

View file

@ -1,25 +0,0 @@
package pro.gravit.launchserver.command.sync;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public class SyncUpdatesCacheCommand extends Command {
public SyncUpdatesCacheCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "sync updates cache";
}
@Override
public void invoke(String... args) throws Exception {
server.updatesManager.readUpdatesFromCache();
}
}

View file

@ -1,42 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public final class SyncUpdatesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncUpdatesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[subdirs...]";
}
@Override
public String getUsageDescription() {
return "Resync updates dir";
}
@Override
public void invoke(String... args) throws IOException {
Set<String> dirs = null;
if (args.length > 0) { // Hash all updates dirs
dirs = new HashSet<>(args.length);
Collections.addAll(dirs, args);
}
// Hash updates dir
server.syncUpdatesDir(dirs);
logger.info("Updates dir successfully resynced");
}
}

View file

@ -4,6 +4,7 @@
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
@ -137,19 +138,18 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
public Path process(PipelineContext context) throws IOException {
if (!component.enabled) {
return inputFile;
return null;
}
LauncherBuildTask task = server.launcherBinary.getTaskBefore((x) -> proguardTaskName.equals(x.getName())).get();
Path lastPath = server.launcherBinary.nextPath(task);
Path lastPath = context.getLastest();
if(Files.notExists(lastPath)) {
logger.error("{} not exist. Multi-Release JAR fix not applied!", lastPath);
return inputFile;
return null;
}
Path outputPath = server.launcherBinary.nextPath(this);
Path outputPath = context.makeTempPath("multirelease-fix", "jar");
try(ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outputPath.toFile()))) {
try(ZipInputStream input = new ZipInputStream(new FileInputStream(inputFile.toFile()))) {
try(ZipInputStream input = new ZipInputStream(new FileInputStream(lastPath.toFile()))) {
ZipEntry entry = input.getNextEntry();
while(entry != null) {
ZipEntry newEntry = new ZipEntry(entry.getName());
@ -193,8 +193,8 @@ public String getName() {
}
@Override
public Path process(Path inputFile) throws IOException {
Path outputJar = server.launcherBinary.nextLowerPath(this);
public Path process(PipelineContext context) throws IOException {
Path outputJar = context.makeTempPath("proguard", "jar");
if (component.enabled) {
if (!checkJMods(IOHelper.JVM_DIR.resolve("jmods"))) {
throw new RuntimeException("Java path: %s is not JDK! Please install JDK".formatted(IOHelper.JVM_DIR));
@ -221,7 +221,7 @@ public Path process(Path inputFile) throws IOException {
);
}
args.add("proguard.ProGuard");
proguardConf.buildConfig(args, inputFile, outputJar, jfxPath == null ? new Path[0] : new Path[]{jfxPath});
proguardConf.buildConfig(args, context.getLastest(), outputJar, jfxPath == null ? new Path[0] : new Path[]{jfxPath});
Process process = new ProcessBuilder()
.command(args)
@ -240,8 +240,9 @@ public Path process(Path inputFile) throws IOException {
} catch (Exception e) {
logger.error(e);
}
} else
IOHelper.copy(inputFile, outputJar);
} else {
return null;
}
return outputJar;
}
}

View file

@ -8,8 +8,8 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.auth.profiles.LocalProfileProvider;
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.profiles.LocalProfilesProvider;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.StdProtectHandler;
import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
@ -28,7 +28,7 @@
public final class LaunchServerConfig {
private final static List<String> oldMirrorList = List.of("https://mirror.gravit.pro/5.2.x/", "https://mirror.gravit.pro/5.3.x/",
"https://mirror.gravitlauncher.com/5.2.x/", "https://mirror.gravitlauncher.com/5.3.x/", "https://mirror.gravitlauncher.com/5.4.x/",
"https://mirror.gravitlauncher.com/5.5.x/");
"https://mirror.gravitlauncher.com/5.5.x/", "https://mirror.gravitlauncher.com/5.6.x/");
private transient final Logger logger = LogManager.getLogger();
public String projectName;
public String[] mirrors;
@ -39,7 +39,7 @@ public final class LaunchServerConfig {
// Handlers & Providers
public ProtectHandler protectHandler;
public Map<String, Component> components;
public ProfileProvider profileProvider = new LocalProfileProvider();
public ProfilesProvider profilesProvider = new LocalProfilesProvider();
public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
public NettyConfig netty;
public LauncherConf launcher;
@ -49,7 +49,7 @@ public final class LaunchServerConfig {
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
LaunchServerConfig newConfig = new LaunchServerConfig();
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.6.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.7.x/"};
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
newConfig.auth = new HashMap<>();
AuthProviderPair a = new AuthProviderPair(new RejectAuthCoreProvider(),
@ -85,7 +85,8 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
newConfig.components.put("authLimiter", authLimiterComponent);
ProGuardComponent proGuardComponent = new ProGuardComponent();
newConfig.components.put("proguard", proGuardComponent);
newConfig.profileProvider = new LocalProfileProvider();
newConfig.profilesProvider = new LocalProfilesProvider();
newConfig.updatesProvider = new LocalUpdatesProvider();
return newConfig;
}
@ -151,7 +152,7 @@ public void verify() {
for (int i = 0; i < mirrors.length; ++i) {
if (mirrors[i] != null && oldMirrorList.contains(mirrors[i])) {
logger.warn("Replace mirror '{}' to 'https://mirror.gravitlauncher.com/5.6.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'", mirrors[i]);
mirrors[i] = "https://mirror.gravitlauncher.com/5.6.x/";
mirrors[i] = "https://mirror.gravitlauncher.com/5.7.x/";
}
}
}
@ -167,9 +168,9 @@ public void init(LaunchServer.ReloadType type) {
server.registerObject("protectHandler", protectHandler);
protectHandler.init(server);
}
if(profileProvider != null) {
server.registerObject("profileProvider", profileProvider);
profileProvider.init(server);
if(profilesProvider != null) {
server.registerObject("profileProvider", profilesProvider);
profilesProvider.init(server);
}
if(updatesProvider != null) {
server.registerObject("updatesProvider", updatesProvider);
@ -215,9 +216,9 @@ public void close(LaunchServer.ReloadType type) {
server.unregisterObject("protectHandler", protectHandler);
protectHandler.close();
}
if(profileProvider != null) {
server.unregisterObject("profileProvider", profileProvider);
profileProvider.close();
if(profilesProvider != null) {
server.unregisterObject("profilesProvider", profilesProvider);
profilesProvider.close();
}
if(updatesProvider != null) {
server.unregisterObject("updatesProvider", updatesProvider);

View file

@ -1,31 +0,0 @@
package pro.gravit.launchserver.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.SecurityHelper;
public class LaunchServerRuntimeConfig {
private transient final Logger logger = LogManager.getLogger();
public String passwordEncryptKey;
public String runtimeEncryptKey;
public String unlockSecret;
public String registerApiKey;
public String clientCheckSecret;
public long buildNumber;
public void verify() {
if (passwordEncryptKey == null) logger.error("[RuntimeConfig] passwordEncryptKey must not be null");
if (clientCheckSecret == null) {
logger.warn("[RuntimeConfig] clientCheckSecret must not be null");
clientCheckSecret = SecurityHelper.randomStringToken();
}
}
public void reset() {
passwordEncryptKey = SecurityHelper.randomStringToken();
runtimeEncryptKey = SecurityHelper.randomStringAESKey();
registerApiKey = SecurityHelper.randomStringToken();
clientCheckSecret = SecurityHelper.randomStringToken();
buildNumber = 0;
}
}

View file

@ -0,0 +1,67 @@
package pro.gravit.launchserver.helper;
import com.google.gson.JsonObject;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.HttpRequester;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class AssetsDirHelper {
public static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
public static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
public static List<Downloader.SizedFile> makeToDownloadFiles(AssetInfo assetInfo, HashedDir updatesDir) {
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : assetInfo.assets.getAsJsonObject("objects").entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
if (updatesDir.tryFindRecursive(path).isFound()) {
continue;
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
return toDownload;
}
public static AssetInfo getAssetInfo(HttpRequester requester, String versionName) throws IOException {
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null;
for (var e : versions.versions) {
if (e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
return new AssetInfo(assetIndex, assets);
}
public record AssetInfo(String assetIndex, JsonObject assets) {
}
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
}

View file

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

View file

@ -47,7 +47,7 @@ public void init() {
MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get();
mainTask.preBuildHook.registerHook((buildContext) -> {
for (ModuleEntity e : launcherModules) {
if (e.propertyMap != null) buildContext.task.properties.putAll(e.propertyMap);
if (e.propertyMap != null) buildContext.properties.putAll(e.propertyMap);
if(e.modernModule) {
buildContext.clientModules.add(e.moduleMainClass);
} else {

View file

@ -191,7 +191,7 @@ public PlayerProfile getPlayerProfile(Client client) {
playerProfile = getPlayerProfile(client.auth, user);
if (playerProfile != null) return playerProfile;
if (client.auth.textureProvider != null) {
return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getTitle(), client.auth.textureProvider, new HashMap<>());
return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getName(), client.auth.textureProvider, new HashMap<>());
}
// Return combined profile
return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>());
@ -282,14 +282,6 @@ public AuthRequest.AuthPasswordInterface decryptPassword(AuthRequest.AuthPasswor
}
private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.AuthPasswordInterface password) throws AuthException {
if (password instanceof AuthAESPassword authAESPassword) {
try {
return new AuthPlainPassword(IOHelper.decode(SecurityHelper.decrypt(server.runtime.passwordEncryptKey
, authAESPassword.password)));
} catch (Exception ignored) {
throw new AuthException("Password decryption error");
}
}
if (password instanceof AuthRSAPassword authRSAPassword) {
try {
Cipher cipher = SecurityHelper.newRSADecryptCipher(server.keyAgreementManager.rsaPrivateKey);

View file

@ -14,7 +14,7 @@
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
@ -48,7 +48,7 @@ public void registerAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers));
builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers));
builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers));
builder.registerTypeAdapter(ProfileProvider.class, new UniversalJsonAdapter<>(ProfileProvider.providers));
builder.registerTypeAdapter(ProfilesProvider.class, new UniversalJsonAdapter<>(ProfilesProvider.providers));
builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
modulesManager.invokeEvent(new PreGsonPhase(builder));
//ClientWebSocketService.appendTypeAdapters(builder);

View file

@ -1,45 +0,0 @@
package pro.gravit.launchserver.manangers;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import java.io.IOException;
import java.util.*;
public class UpdatesManager {
private final LaunchServer server;
public UpdatesManager(LaunchServer server) {
this.server = server;
}
@Deprecated
public void readUpdatesFromCache() {
}
@Deprecated
public void readUpdatesDir() {
}
@Deprecated
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
server.config.updatesProvider.sync(dirs);
}
@Deprecated
public HashSet<String> getUpdatesList() {
return new HashSet<>();
}
@Deprecated
public HashedDir getUpdate(String name) {
return server.config.updatesProvider.getUpdatesDir(name);
}
@Deprecated
public void addUpdate(String name, HashedDir dir) {
throw new UnsupportedOperationException();
}
}

View file

@ -2,13 +2,13 @@
import pro.gravit.launcher.base.modules.LauncherInitContext;
import pro.gravit.launcher.base.modules.LauncherModule;
import pro.gravit.launcher.base.modules.LauncherModuleInfo;
import pro.gravit.launcher.base.modules.LauncherModuleInfoBuilder;
import pro.gravit.launcher.base.modules.events.InitPhase;
import pro.gravit.utils.Version;
public class LaunchServerCoreModule extends LauncherModule {
public LaunchServerCoreModule() {
super(new LauncherModuleInfo("LaunchServerCore", Version.getVersion()));
super(new LauncherModuleInfoBuilder().setName("LaunchServerCore").setVersion(Version.getVersion()).createLauncherModuleInfo());
}
@Override

View file

@ -5,6 +5,7 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.util.HashMap;
@ -19,7 +20,7 @@ public class Client {
public String auth_id;
public long timestamp;
public AuthResponse.ConnectTypes type;
public ClientProfile profile;
public ProfilesProvider.CompletedProfile profile;
public boolean isAuth;
public boolean checkSign;
public ClientPermissions permissions;

View file

@ -3,6 +3,7 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.CurrentUserRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -12,6 +13,13 @@ public static CurrentUserRequestEvent.UserInfo collectUserInfoFromClient(LaunchS
CurrentUserRequestEvent.UserInfo result = new CurrentUserRequestEvent.UserInfo();
if (client.auth != null && client.isAuth && client.username != null) {
result.playerProfile = server.authManager.getPlayerProfile(client);
if(server.config.protectHandler.allowGetAccessToken(new AuthResponse.AuthContext(client, client.username,
null, null, client.type, client.auth))) {
UserSession session = client.sessionObject;
if(session != null) {
result.accessToken = session.getMinecraftAccessToken();
}
}
}
result.permissions = client.permissions;
return result;

View file

@ -3,8 +3,9 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -13,21 +14,22 @@
import java.util.Set;
public class ProfilesResponse extends SimpleResponse {
@Deprecated
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = server.getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
profileList = new ArrayList<>(4);
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
Set<ProfilesProvider.UncompletedProfile> serverProfiles = server.config.profilesProvider.getProfiles(client);
List<ClientProfile> profiles = new ArrayList<>();
for(var uncompleted : serverProfiles) {
if(uncompleted instanceof ProfilesProvider.CompletedProfile completed) {
profiles.add(completed.getProfile());
} else {
profileList = List.copyOf(serverProfiles);
profiles.add(new ClientProfileBuilder()
.setUuid(uncompleted.getUuid())
.setTitle(uncompleted.getName())
.setInfo(uncompleted.getDescription())
.createClientProfile());
}
return profileList;
}
return profiles;
}
@Override
@ -37,10 +39,6 @@ public String getType() {
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetProfiles(client)) {
sendError("Access denied");
return;
}
sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client)));
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
}
}

View file

@ -2,16 +2,15 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.SetProfileRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.utils.HookException;
import java.util.Collection;
import java.util.UUID;
public class SetProfileResponse extends SimpleResponse {
public String client;
public UUID uuid;
public String tag;
@Override
public String getType() {
@ -25,20 +24,13 @@ public void execute(ChannelHandlerContext ctx, Client client) {
} catch (HookException e) {
sendError(e.getMessage());
}
Collection<ClientProfile> profiles = server.getProfiles();
for (ClientProfile p : profiles) {
if (p.getTitle().equals(this.client)) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler &&
!profilesProtectHandler.canChangeProfile(p, client)) {
sendError("Access denied");
return;
}
client.profile = p;
sendResult(new SetProfileRequestEvent(p));
return;
}
}
var profile = server.config.profilesProvider.get(uuid, tag);
if(profile == null) {
sendError("Profile not found");
return;
}
client.profile = profile;
sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag()));
}
@Override

View file

@ -9,6 +9,7 @@
import pro.gravit.launcher.base.events.request.LauncherRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -43,28 +44,18 @@ public void execute(ChannelHandlerContext ctx, Client client) {
bytes = Base64.getDecoder().decode(hash);
else
bytes = digest;
if (launcher_type == 1) // JAR
{
byte[] hash = server.launcherBinary.getDigest();
if (hash == null)
service.sendObjectAndClose(ctx, new LauncherRequestEvent(true, server.config.netty.launcherURL));
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, null, 0));
UpdatesProvider.UpdateVariant variant = UpdatesProvider.UpdateVariant.JAR;
if(launcher_type == 2) {
variant = UpdatesProvider.UpdateVariant.EXE;
}
} else if (launcher_type == 2) //EXE
{
byte[] hash = server.launcherEXEBinary.getDigest();
if (hash == null) sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL));
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
byte[] hashToCheck = bytes;
UpdatesProvider.UpdateInfo info = server.config.updatesProvider.checkUpdates(variant, new UpdatesProvider.BuildSecretsCheck(secureHash, secureSalt, hashToCheck));
if (info != null) {
sendResult(new LauncherRequestEvent(true, info.url()));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0));
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, null, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
}
} else sendError("Request launcher type error");
}
public String createLauncherExtendedToken() {
@ -76,14 +67,6 @@ public String createLauncherExtendedToken() {
.compact();
}
private boolean checkSecure(String hash, String salt) {
if (hash == null || salt == null) return false;
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
server.runtime.clientCheckSecret.concat(".").concat(salt));
byte[] launcher_hash = Base64.getDecoder().decode(hash);
return Arrays.equals(normal_hash, launcher_hash);
}
public static class LauncherTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private final JwtParser parser;
private final Logger logger = LogManager.getLogger();

View file

@ -3,7 +3,6 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.UpdateRequestEvent;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -19,15 +18,22 @@ public String getType() {
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetUpdates(dirName, client)) {
sendError("Access denied");
return;
}
if (dirName == null) {
sendError("Invalid request");
return;
}
HashedDir dir = server.updatesManager.getUpdate(dirName);
if(client.profile == null) {
sendError("Profile not setted");
return;
}
HashedDir dir = null;
if(dirName.equals(client.profile.getProfile().getDir())) {
dir = client.profile.getClientDir();
} else if(dirName.equals(client.profile.getProfile().getAssetDir())) {
dir = client.profile.getAssetDir();
} else {
dir = server.config.profilesProvider.getUnconnectedDirectory(dirName);
}
if (dir == null) {
sendError("Directory %s not found".formatted(dirName));
return;

View file

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

View file

@ -6,7 +6,6 @@
import org.junit.jupiter.api.io.TempDir;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.impl.TestLaunchServerConfigManager;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
@ -33,13 +32,11 @@ public static void prepare() throws Throwable {
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
Launcher.gsonManager.initGson();
LaunchServerRuntimeConfig runtimeConfig = new LaunchServerRuntimeConfig();
LaunchServerBuilder builder = new LaunchServerBuilder();
launchServerConfigManager = new TestLaunchServerConfigManager();
builder.setDir(dir)
.setEnv(LaunchServer.LaunchServerEnv.TEST)
.setConfig(config)
.setRuntimeConfig(runtimeConfig)
.setCertificateManager(new CertificateManager())
.setLaunchServerConfigManager(launchServerConfigManager)
.setModulesManager(modulesManager)

View file

@ -7,7 +7,6 @@
import org.junit.jupiter.api.io.TempDir;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.impl.TestLaunchServerConfigManager;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
@ -33,12 +32,10 @@ public static void prepare() throws Throwable {
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
Launcher.gsonManager.initGson();
LaunchServerRuntimeConfig runtimeConfig = new LaunchServerRuntimeConfig();
LaunchServerBuilder builder = new LaunchServerBuilder();
builder.setDir(dir)
.setEnv(LaunchServer.LaunchServerEnv.TEST)
.setConfig(config)
.setRuntimeConfig(runtimeConfig)
.setCertificateManager(new CertificateManager())
.setLaunchServerConfigManager(new TestLaunchServerConfigManager())
.setModulesManager(modulesManager)

View file

@ -2,16 +2,12 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
public class TestLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
public LaunchServerConfig config;
public LaunchServerRuntimeConfig runtimeConfig;
public TestLaunchServerConfigManager() {
config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
runtimeConfig = new LaunchServerRuntimeConfig();
runtimeConfig.reset();
}
@Override
@ -19,18 +15,8 @@ public LaunchServerConfig readConfig() {
return config;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
return runtimeConfig;
}
@Override
public void writeConfig(LaunchServerConfig config) {
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
}
}

View file

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

View file

@ -2,14 +2,18 @@
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.*;
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;
@ -19,25 +23,16 @@
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;
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;
import java.util.concurrent.atomic.AtomicBoolean;
@ -48,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) {
@ -176,37 +169,9 @@ 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 {
var config = Launcher.getConfig();
config.apply();
//Launcher.modulesManager = new ClientModuleManager(this);
ClientPreGuiPhase event = new ClientPreGuiPhase(null);
LauncherEngine.modulesManager.invokeEvent(event);
@ -215,7 +180,7 @@ public void start(String... args) throws Throwable {
runtimeProvider.init(clientInstance);
//runtimeProvider.preLoad();
if (!Request.isAvailable()) {
String address = Launcher.getConfig().address;
String address = config.address;
LogHelper.debug("Start async connection to %s", address);
RequestService service;
try {
@ -243,10 +208,22 @@ 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,
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();
@ -255,8 +232,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

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

View file

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

View file

@ -0,0 +1,266 @@
package pro.gravit.launcher.runtime.backend;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.optional.OptionalView;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalActionFile;
import pro.gravit.launcher.core.api.LauncherAPIHolder;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.core.hasher.FileNameMatcher;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.hasher.HashedEntry;
import pro.gravit.launcher.core.hasher.HashedFile;
import pro.gravit.launcher.runtime.client.DirBridge;
import pro.gravit.launcher.runtime.utils.AssetIndexHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class ClientDownloadImpl {
private LauncherBackendImpl backend;
ClientDownloadImpl(LauncherBackendImpl backend) {
this.backend = backend;
}
CompletableFuture<LauncherBackendAPI.ReadyProfile> downloadProfile(ClientProfile profile, ProfileSettingsImpl settings, LauncherBackendAPI.DownloadCallback callback) {
AtomicReference<DownloadedDir> clientRef = new AtomicReference<>();
AtomicReference<DownloadedDir> assetRef = new AtomicReference<>();
AtomicReference<DownloadedDir> javaRef = new AtomicReference<>();
return LauncherAPIHolder.profile().changeCurrentProfile(profile)
.thenCompose(vv -> downloadDir(profile.getDir(), profile.getClientUpdateMatcher(), settings.view, callback)).thenCompose((clientDir -> {
clientRef.set(clientDir);
return downloadAsset(profile.getAssetDir(), profile.getAssetUpdateMatcher(), profile.getAssetIndex(), callback);
})).thenCompose(assetDir -> {
assetRef.set(assetDir);
return CompletableFuture.completedFuture((DownloadedDir)null); // TODO Custom Java
}).thenCompose(javaDir -> {
javaRef.set(javaDir);
return CompletableFuture.completedFuture(null);
}).thenApply(v -> {
return new ReadyProfileImpl(backend, profile, settings, clientRef.get(), assetRef.get(), javaRef.get());
});
}
CompletableFuture<DownloadedDir> downloadAsset(String dirName, FileNameMatcher matcher, String assetIndexString, LauncherBackendAPI.DownloadCallback callback) {
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
Path assetIndexPath = targetDir.resolve("indexes").resolve(assetIndexString+".json");
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName).thenComposeAsync((response) -> {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_ASSET_VERIFY);
return verifyAssetIndex(assetIndexString, response, assetIndexPath, targetDir);
}, backend.executorService)
.thenApply(assetData -> {
HashedDir dir = assetData.updateInfo.getHashedDir();
AssetIndexHelper.modifyHashedDir(assetData.index, dir);
return new VirtualUpdateInfo(dir, assetData.updateInfo.getUrl());
})
.thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e));
}
private CompletableFuture<AssetData> verifyAssetIndex(String assetIndexString, ProfileFeatureAPI.UpdateInfo response, Path assetIndexPath, Path targetDir) {
var assetIndexRelPath = String.format("indexes/%s.json", assetIndexString);
var assetIndexHash = response.getHashedDir().findRecursive(assetIndexRelPath);
if(!(assetIndexHash.entry instanceof HashedFile assetIndexHashFile)) {
return CompletableFuture.failedFuture(new FileNotFoundException(String.format("Asset Index %s not found in the server response", assetIndexString)));
}
try {
if(Files.exists(assetIndexPath) && assetIndexHashFile.isSame(assetIndexPath, true)) {
var assetIndex = AssetIndexHelper.parse(assetIndexPath);
return CompletableFuture.completedFuture(new AssetData(response, assetIndex));
} else {
var downloader = Downloader.newDownloader(backend.executorService);
var list = new LinkedList<Downloader.SizedFile>();
list.add(new Downloader.SizedFile(assetIndexRelPath, assetIndexRelPath, assetIndexHashFile.size));
return downloader.downloadFiles(list, response.getUrl(), targetDir, null, backend.executorService, 1).thenComposeAsync(v -> {
try {
var assetIndex = AssetIndexHelper.parse(assetIndexPath);
return CompletableFuture.completedFuture(new AssetData(response, assetIndex));
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
}, backend.executorService);
}
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
CompletableFuture<DownloadedDir> downloadDir(String dirName, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback) {
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName)
.thenCompose(response -> downloadDir(targetDir, response, matcher, callback, e -> e));
}
CompletableFuture<DownloadedDir> downloadDir(String dirName, FileNameMatcher matcher, OptionalView view, LauncherBackendAPI.DownloadCallback callback) {
Path targetDir = DirBridge.dirUpdates.resolve(dirName);
return LauncherAPIHolder.profile().fetchUpdateInfo(dirName)
.thenCompose(response -> {
var hashedDir = response.getHashedDir();
var remap = applyOptionalMods(view, hashedDir);
return downloadDir(targetDir, new VirtualUpdateInfo(hashedDir, response.getUrl()), matcher, callback, makePathRemapperFunction(remap));
});
}
CompletableFuture<DownloadedDir> downloadDir(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, FileNameMatcher matcher, LauncherBackendAPI.DownloadCallback callback, Function<String, String> remap) {
return CompletableFuture.supplyAsync(() -> {
try {
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_HASHING);
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);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, backend.executorService).thenComposeAsync((diff) -> {
return downloadFiles(targetDir, updateInfo, callback, diff, remap);
}, backend.executorService).thenApply(v -> new DownloadedDir(updateInfo.getHashedDir(), targetDir));
}
private CompletableFuture<HashedDir.Diff> downloadFiles(Path targetDir, ProfileFeatureAPI.UpdateInfo updateInfo, LauncherBackendAPI.DownloadCallback callback, HashedDir.Diff diff, Function<String, String> remap) {
Downloader downloader = Downloader.newDownloader(backend.executorService);
try {
var files = collectFilesAndCreateDirectories(targetDir, diff.mismatch, remap);
long total = 0;
for(var e : files) {
total += e.size;
}
callback.onTotalDownload(total);
callback.onCanCancel(downloader::cancel);
return downloader.downloadFiles(files, updateInfo.getUrl(), targetDir, new Downloader.DownloadCallback() {
@Override
public void apply(long fullDiff) {
callback.onCurrentDownloaded(fullDiff);
}
@Override
public void onComplete(Path path) {
}
}, backend.executorService, 4).thenComposeAsync(v -> {
callback.onCanCancel(null);
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DELETE_EXTRA);
try {
deleteExtraDir(targetDir, diff.extra, diff.extra.flag);
} catch (IOException ex) {
return CompletableFuture.failedFuture(ex);
}
callback.onStage(LauncherBackendAPI.DownloadCallback.STAGE_DONE_PART);
return CompletableFuture.completedFuture(diff);
}, backend.executorService);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
private List<Downloader.SizedFile> collectFilesAndCreateDirectories(Path dir, HashedDir mismatch, Function<String, String> pathRemapper) throws IOException {
List<Downloader.SizedFile> files = new ArrayList<>();
mismatch.walk(File.separator, (path, name, entry) -> {
if(entry.getType() == HashedEntry.Type.DIR) {
var dirPath = dir.resolve(path);
try {
if(!Files.exists(dirPath)) {
Files.createDirectory(dirPath);
} else if (!Files.isDirectory(dirPath)) {
throw new IOException(String.format("%s is not a directory", path));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return HashedDir.WalkAction.CONTINUE;
}
String pathFixed = path.replace(File.separatorChar, '/');
files.add(new Downloader.SizedFile(pathFixed, pathRemapper.apply(pathFixed), entry.size()));
return HashedDir.WalkAction.CONTINUE;
});
return files;
}
private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean deleteDir) throws IOException {
for (Map.Entry<String, HashedEntry> mapEntry : subHDir.map().entrySet()) {
String name = mapEntry.getKey();
Path path = subDir.resolve(name);
// Delete list and dirs based on type
HashedEntry entry = mapEntry.getValue();
HashedEntry.Type entryType = entry.getType();
switch (entryType) {
case FILE -> Files.delete(path);
case DIR -> deleteExtraDir(path, (HashedDir) entry, deleteDir || entry.flag);
default -> throw new AssertionError("Unsupported hashed entry type: " + entryType.name());
}
}
// Delete!
if (deleteDir) {
Files.delete(subDir);
}
}
private Function<String, String> makePathRemapperFunction(LinkedList<PathRemapperData> map) {
return (path) -> {
for(var e : map) {
if(path.startsWith(e.key)) {
return e.value;
}
}
return path;
};
}
private LinkedList<PathRemapperData> applyOptionalMods(OptionalView view, HashedDir hdir) {
for (OptionalAction action : view.getDisabledActions()) {
if (action instanceof OptionalActionFile optionalActionFile) {
optionalActionFile.disableInHashedDir(hdir);
}
}
LinkedList<PathRemapperData> pathRemapper = new LinkedList<>();
Set<OptionalActionFile> fileActions = view.getActionsByClass(OptionalActionFile.class);
for (OptionalActionFile file : fileActions) {
file.injectToHashedDir(hdir);
file.files.forEach((k, v) -> {
if (v == null || v.isEmpty()) return;
pathRemapper.add(new PathRemapperData(v, k)); //reverse (!)
LogHelper.dev("Remap prepare %s to %s", v, k);
});
}
pathRemapper.sort(Comparator.comparingInt(c -> -c.key.length())); // Support deep remap
return pathRemapper;
}
private record PathRemapperData(String key, String value) {
}
record AssetData(ProfileFeatureAPI.UpdateInfo updateInfo, AssetIndexHelper.AssetIndex index) {
}
record DownloadedDir(HashedDir dir, Path path) {
}
record VirtualUpdateInfo(HashedDir dir, String url) implements ProfileFeatureAPI.UpdateInfo {
@Override
public HashedDir getHashedDir() {
return dir;
}
@Override
public String getUrl() {
return url;
}
}
}

View file

@ -0,0 +1,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

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

View file

@ -0,0 +1,411 @@
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;
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.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.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;
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;
import java.util.stream.Collectors;
public class LauncherBackendImpl implements LauncherBackendAPI, TextureUploadExtension {
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;
// Hardware
private volatile ECKeyHolder ecKeyHolder;
// Data
private volatile Map<UUID, 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
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();
ecKeyHolder = new ECKeyHolder();
ecKeyHolder.readKeys();
DirBridge.dirUpdates = DirBridge.defaultUpdatesDir;
}
@Override
public CompletableFuture<LauncherInitData> init() {
try {
doInit();
} catch (Throwable e) {
return CompletableFuture.failedFuture(e);
}
CompletableFuture<CoreFeatureAPI.LauncherUpdateInfo> feature;
if(isTestMode()) {
feature = CompletableFuture.completedFuture(new CoreFeatureAPI.LauncherUpdateInfo(null, "Unknown", false, false));
} else {
feature = LauncherAPIHolder.core().checkUpdates();
}
return feature.thenCombineAsync(LauncherAPIHolder.core().getAuthMethods(), (updatesInfo, authMethods) -> {
if(updatesInfo.required()) {
try {
LauncherUpdater.prepareUpdate(URI.create(updatesInfo.url()).toURL());
} catch (Exception e) {
throw new RuntimeException(e);
}
callback.onShutdown();
LauncherUpdater.restart();
}
return new LauncherInitData(authMethods);
}, executorService);
}
public AuthFeatureAPI.AuthToken getAuthToken() {
return backendSettings.auth.toToken();
}
public AuthMethod getAuthMethod() {
return authMethod;
}
@Override
public void selectAuthMethod(AuthMethod method) {
this.authMethod = method;
LauncherAPIHolder.changeAuthId(method.getName());
}
@Override
public CompletableFuture<SelfUser> tryAuthorize() {
if(this.authMethod == null) {
return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod"));
}
if(backendSettings.auth == null) {
return CompletableFuture.failedFuture(new LauncherBackendException("Auth data not found"));
}
if(backendSettings.auth.expireIn > 0 && LocalDateTime.ofInstant(Instant.ofEpochMilli(backendSettings.auth.expireIn), ZoneOffset.UTC).isBefore(LocalDateTime.now(ZoneOffset.UTC))) {
return LauncherAPIHolder.auth().refreshToken(backendSettings.auth.refreshToken).thenCompose((response) -> {
setAuthToken(response);
return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true);
}).thenApply((user) -> {
onAuthorize(user);
return user;
});
}
return LauncherAPIHolder.auth().restore(backendSettings.auth.accessToken, true).thenApply((user) -> {
onAuthorize(user);
return user;
});
}
private void setAuthToken(AuthFeatureAPI.AuthToken authToken) {
backendSettings.auth = new BackendSettings.AuthorizationData();
backendSettings.auth.accessToken = authToken.getAccessToken();
backendSettings.auth.refreshToken = authToken.getRefreshToken();
if(authToken.getExpire() <= 0) {
backendSettings.auth.expireIn = 0;
}
backendSettings.auth.expireIn = LocalDateTime.now(ZoneOffset.UTC)
.plus(authToken.getExpire(), ChronoUnit.MILLIS).
toEpochSecond(ZoneOffset.UTC);
}
private void onAuthorize(SelfUser selfUser) {
this.selfUser = selfUser;
permissions = selfUser.getPermissions();
callback.onAuthorize(selfUser);
if(processHardwareFuture == null) {
processHardwareFuture = processHardware();
}
}
@Override
public CompletableFuture<SelfUser> authorize(String login, AuthMethodPassword password) {
if(this.authMethod == null) {
return CompletableFuture.failedFuture(new LauncherBackendException("This method call not allowed before select authMethod"));
}
return LauncherAPIHolder.auth().auth(login, password).thenApply((response) -> {
setAuthToken(response.authToken());
onAuthorize(response.user());
return response.user();
});
}
@Override
public CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles() {
return LauncherAPIHolder.profile().getProfiles().thenApply((profiles) -> {
onProfiles(profiles);
callback.onProfiles(profiles);
return profiles;
});
}
private void onProfiles(List<ProfileFeatureAPI.ClientProfile> profiles) {
this.profiles = profiles.stream().collect(Collectors.toMap(ProfileFeatureAPI.ClientProfile::getUUID, x -> x));
for(var e : backendSettings.settings.entrySet()) {
ClientProfile profile = (ClientProfile) this.profiles.get(e.getKey());
if(profile == null) {
continue;
}
e.getValue().initAfterGson(profile, this);
}
}
@Override
public ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile) {
var settings = backendSettings.settings.get(profile.getUUID());
if(settings == null) {
settings = new ProfileSettingsImpl((ClientProfile) profile);
settings.backend = this;
settings.updateEnabledMods();
} else {
if(settings.backend == null) {
settings.initAfterGson((ClientProfile) profile, this);
}
settings = settings.copy();
}
return settings;
}
@Override
public void saveClientProfileSettings(ClientProfileSettings settings) {
var impl = (ProfileSettingsImpl) settings;
impl.updateEnabledMods();
backendSettings.settings.put(impl.profile.getUUID(), impl);
}
@Override
public CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback) {
return clientDownloadImpl.downloadProfile((ClientProfile) profile, (ProfileSettingsImpl) settings, callback);
}
@Override
public CompletableFuture<byte[]> fetchTexture(Texture texture) {
return CompletableFuture.failedFuture(new UnsupportedOperationException());
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<List<Java>> getAvailableJava() {
if(availableJavas == null) {
if(availableJavasFuture == null) {
availableJavasFuture = CompletableFuture.supplyAsync(() -> {
return (List) JavaHelper.findJava(); // TODO: Custom Java
}, executorService).thenApply(e -> {
availableJavas = e;
return e;
});
}
return availableJavasFuture;
}
return CompletableFuture.completedFuture(availableJavas);
}
@Override
public CompletableFuture<ServerPingInfo> pingServer(ProfileFeatureAPI.ClientProfile profile) {
return pingFutures.computeIfAbsent(profile.getUUID(), (k) -> {
CompletableFuture<ServerPingInfo> future = new CompletableFuture<>();
executorService.submit(() -> {
try {
ServerPinger pinger = new ServerPinger((ClientProfile) profile);
future.complete(pinger.ping());
} catch (Throwable e) {
future.completeExceptionally(e);
}
});
return future;
});
}
@Override
public void registerUserSettings(String name, Class<? extends UserSettings> clazz) {
UserSettings.providers.register(name, clazz);
}
@Override
public UserSettings getUserSettings(String name, Function<String, UserSettings> ifNotExist) {
return allSettings.userSettings.computeIfAbsent(name, ifNotExist);
}
@Override
public UserPermissions getPermissions() {
return permissions;
}
@Override
public boolean hasPermission(String permission) {
return permissions.hasPerm(permission);
}
@Override
public String getUsername() {
return selfUser == null ? "Player" : getUsername();
}
@Override
public SelfUser getSelfUser() {
return selfUser;
}
@Override
public boolean isTestMode() {
try {
return DebugMain.IS_DEBUG.get();
} catch (Throwable ex) {
return false;
}
}
@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;
}
@Override
public void shutdown() {
if(executorService != null) {
executorService.shutdownNow();
}
if(settingsManager != null) {
try {
settingsManager.saveConfig();
} catch (IOException e) {
LogHelper.error("Config not saved", e);
}
}
}
@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());
});
}
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;
}
}

View file

@ -0,0 +1,223 @@
package pro.gravit.launcher.runtime.backend;
import oshi.SystemInfo;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.optional.OptionalFile;
import pro.gravit.launcher.base.profiles.optional.OptionalView;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
import pro.gravit.launcher.runtime.utils.SystemMemory;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.JavaHelper;
import java.util.*;
import java.util.concurrent.ExecutionException;
public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSettings {
transient ClientProfile profile;
transient LauncherBackendImpl backend;
@LauncherNetworkAPI
private Map<MemoryClass, Long> ram;
@LauncherNetworkAPI
private Set<Flag> flags;
@LauncherNetworkAPI
private Set<String> enabled;
@LauncherNetworkAPI
private String saveJavaPath;
transient OptionalView view;
transient volatile JavaHelper.JavaVersion selectedJava;
public ProfileSettingsImpl() {
}
public ProfileSettingsImpl(ClientProfile profile) {
this.profile = profile;
var def = profile.getSettings();
this.ram = new HashMap<>();
this.ram.put(MemoryClass.TOTAL, ((long)def.ram) << 20);
this.flags = new HashSet<>();
if(def.autoEnter) {
this.flags.add(Flag.AUTO_ENTER);
}
if(def.fullScreen) {
this.flags.add(Flag.FULLSCREEN);
}
this.view = new OptionalView(profile);
}
@Override
public long getReservedMemoryBytes(MemoryClass memoryClass) {
return ram.getOrDefault(memoryClass, 0L);
}
@Override
public long getMaxMemoryBytes(MemoryClass memoryClass) {
try {
return SystemMemory.getPhysicalMemorySize();
} catch (Throwable e) {
SystemInfo systemInfo = new SystemInfo();
return systemInfo.getHardware().getMemory().getTotal();
}
}
@Override
public void setReservedMemoryBytes(MemoryClass memoryClass, long value) {
this.ram.put(memoryClass, value);
}
@Override
public Set<Flag> getFlags() {
return Collections.unmodifiableSet(flags);
}
@Override
public Set<Flag> getAvailableFlags() {
Set<Flag> set = new HashSet<>();
set.add(Flag.AUTO_ENTER);
set.add(Flag.FULLSCREEN);
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
set.add(Flag.LINUX_WAYLAND_SUPPORT);
}
if(backend.hasPermission("launcher.debug.skipfilemonitor")) {
set.add(Flag.DEBUG_SKIP_FILE_MONITOR);
}
return set;
}
@Override
public boolean hasFlag(Flag flag) {
return flags.contains(flag);
}
@Override
public void addFlag(Flag flag) {
flags.add(flag);
}
@Override
public void removeFlag(Flag flag) {
flags.remove(flag);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Set<ProfileFeatureAPI.OptionalMod> getAllOptionals() {
return (Set) view.all;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Set<ProfileFeatureAPI.OptionalMod> getEnabledOptionals() {
return (Set) view.enabled;
}
@Override
public void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) {
view.enable((OptionalFile) mod, true, callback::onChanged);
}
@Override
public void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback) {
view.disable((OptionalFile) mod, callback::onChanged);
}
@Override
public JavaHelper.JavaVersion getSelectedJava() {
return selectedJava;
}
@Override
public JavaHelper.JavaVersion getRecommendedJava() {
JavaHelper.JavaVersion result = null;
try {
for(var java : backend.getAvailableJava().get()) {
if(isRecommended(java)) {
return (JavaHelper.JavaVersion) java;
}
if(isCompatible(java)) {
if(result == null) {
result = (JavaHelper.JavaVersion) java;
continue;
}
if(result.getMajorVersion() < java.getMajorVersion()) {
result = (JavaHelper.JavaVersion) java;
}
}
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
return result;
}
@Override
public void setSelectedJava(LauncherBackendAPI.Java java) {
selectedJava = (JavaHelper.JavaVersion) java;
}
@Override
public boolean isRecommended(LauncherBackendAPI.Java java) {
return java.getMajorVersion() == profile.getRecommendJavaVersion();
}
@Override
public boolean isCompatible(LauncherBackendAPI.Java java) {
return java.getMajorVersion() >= profile.getMinJavaVersion() && java.getMajorVersion() <= profile.getMaxJavaVersion();
}
@Override
public ProfileSettingsImpl copy() {
ProfileSettingsImpl cloned = new ProfileSettingsImpl();
cloned.backend = backend;
cloned.profile = profile;
cloned.ram = new HashMap<>(ram);
cloned.flags = new HashSet<>(flags);
cloned.enabled = new HashSet<>(enabled);
if(view != null) {
cloned.view = new OptionalView(profile, view);
}
cloned.selectedJava = selectedJava;
cloned.saveJavaPath = saveJavaPath;
return cloned;
}
public void updateEnabledMods() {
enabled = new HashSet<>();
for(var e : view.enabled) {
enabled.add(e.name);
}
if(selectedJava != null) {
saveJavaPath = selectedJava.getPath().toAbsolutePath().toString();
}
}
public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) {
this.backend = backend;
this.profile = profile;
this.view = new OptionalView(profile);
for(var e : enabled) {
var opt = profile.getOptionalFile(e);
if(opt == null) {
continue;
}
enableOptional(opt, (var1, var2) -> {});
}
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

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

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.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;
@ -69,6 +70,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);
@ -77,6 +84,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");
@ -119,10 +128,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();

View file

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

View file

@ -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<String> getPlayerNames() {
return List.of();
}
}
}

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

@ -50,6 +50,7 @@ public static void initialize() throws Exception {
config.unlockSecret = DebugProperties.UNLOCK_SECRET;
Launcher.setConfig(config);
Launcher.applyLauncherEnv(DebugProperties.ENV);
config.apply();
LauncherEngine.modulesManager = new RuntimeModuleManager();
LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule());
for (String moduleClassName : DebugProperties.MODULE_CLASSES) {

View file

@ -0,0 +1,65 @@
package pro.gravit.launcher.runtime.utils;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.hasher.HashedEntry;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Path;
import java.util.*;
public class AssetIndexHelper {
public static AssetIndex parse(Path path) throws IOException {
try (Reader reader = IOHelper.newReader(path)) {
return Launcher.gsonManager.gson.fromJson(reader, AssetIndex.class);
}
}
public static void modifyHashedDir(AssetIndex index, HashedDir original) {
Set<String> hashes = new HashSet<>();
for (AssetIndexObject obj : index.objects.values()) {
hashes.add(obj.hash);
}
HashedDir objects = (HashedDir) original.getEntry("objects");
List<String> toDeleteDirs = new ArrayList<>(16);
for (Map.Entry<String, HashedEntry> entry : objects.map().entrySet()) {
if (entry.getValue().getType() != HashedEntry.Type.DIR) {
continue;
}
HashedDir dir = (HashedDir) entry.getValue();
List<String> toDelete = new ArrayList<>(16);
for (String hash : dir.map().keySet()) {
if (!hashes.contains(hash)) {
toDelete.add(hash);
}
}
for (String s : toDelete) {
dir.remove(s);
}
if (dir.map().isEmpty()) {
toDeleteDirs.add(entry.getKey());
}
}
for (String s : toDeleteDirs) {
objects.remove(s);
}
}
public static class AssetIndex {
@LauncherNetworkAPI
public boolean virtual;
@LauncherNetworkAPI
public Map<String, AssetIndexObject> objects;
}
public static class AssetIndexObject {
@LauncherNetworkAPI
public String hash;
@LauncherNetworkAPI
public long size;
}
}

View file

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

@ -0,0 +1,10 @@
package pro.gravit.launcher.runtime.utils;
import com.sun.management.OperatingSystemMXBean;
public class SystemMemory {
private static final OperatingSystemMXBean systemMXBean = (OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean();
public static long getPhysicalMemorySize() {
return systemMXBean.getTotalMemorySize();
}
}

View file

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

View file

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

View file

@ -234,7 +234,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();
@ -247,6 +247,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)

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.base;
import pro.gravit.launcher.core.BuildInParams;
import pro.gravit.launcher.core.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor;
import pro.gravit.launcher.core.LauncherTrustManager;
@ -8,6 +9,7 @@
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -122,6 +124,12 @@ public LauncherConfig(String address, Map<String, byte[]> runtime, String projec
runtimeEncryptKey = null;
}
public void apply() {
Version version = Version.getVersion();
BuildInParams.setVersion(new Version(version.major, version.minor, version.patch, (int) buildNumber));
BuildInParams.setProjectName(projectName);
}
public static void initModules(LauncherModulesManager modulesManager) {
if(JVMHelper.JVM_VERSION >= 17) {
modulesClasses.addAll(ModernModulesClass.modulesClasses);

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,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,26 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
this.refreshToken = refreshToken;
this.expire = expire;
}
public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) {
this.accessToken = token.getAccessToken();
this.refreshToken = token.getRefreshToken();
this.expire = token.getExpire();
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public String getRefreshToken() {
return refreshToken;
}
@Override
public long getExpire() {
return expire;
}
}
}

View file

@ -3,6 +3,11 @@
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.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,7 +21,7 @@ public String getType() {
return "currentUser";
}
public static class UserInfo {
public static class UserInfo implements SelfUser {
public ClientPermissions permissions;
public String accessToken;
public PlayerProfile playerProfile;
@ -29,5 +34,35 @@ public UserInfo(ClientPermissions permissions, String accessToken, PlayerProfile
this.accessToken = accessToken;
this.playerProfile = playerProfile;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String getUsername() {
return playerProfile.getUsername();
}
@Override
public UUID getUUID() {
return playerProfile.getUUID();
}
@Override
public Map<String, Texture> getAssets() {
return playerProfile.getAssets();
}
@Override
public Map<String, String> getProperties() {
return playerProfile.getProperties();
}
}
}

View file

@ -1,9 +1,16 @@
package pro.gravit.launcher.base.events.request;
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<AuthAvailabilityDetails> details;
@LauncherNetworkAPI
public String name;
@ -59,5 +67,34 @@ public AuthAvailability(List<AuthAvailabilityDetails> details, String name, Stri
this.visible = visible;
this.features = features;
}
@Override
public List<AuthMethodDetails> getDetails() {
List<AuthMethodDetails> convert = new ArrayList<>();
for(var e : details) {
convert.add(e.toAuthMethodDetails());
}
return convert;
}
@Override
public String getName() {
return name;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public boolean isVisible() {
return visible;
}
@Override
public Set<String> getFeatures() {
return features;
}
}
}

View file

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

@ -12,9 +12,17 @@ public class SetProfileRequestEvent extends RequestEvent {
private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541");
@LauncherNetworkAPI
public final ClientProfile newProfile;
@LauncherNetworkAPI
public final String tag;
public SetProfileRequestEvent(ClientProfile newProfile) {
this.newProfile = newProfile;
this.tag = null;
}
public SetProfileRequestEvent(ClientProfile newProfile, String tag) {
this.newProfile = newProfile;
this.tag = tag;
}
@Override

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

Some files were not shown because too many files have changed in this diff Show more