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) { tasks.register('dumpProguard', Copy) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/proguard" into "$buildDir/libs/proguard-libraries"
from configurations.proguardPack from configurations.proguardPack
} }

View file

@ -2,17 +2,16 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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.modules.events.ClosePhase;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider; 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.EXELauncherBinary;
import pro.gravit.launchserver.binary.JARLauncherBinary; import pro.gravit.launchserver.binary.JARLauncherBinary;
import pro.gravit.launchserver.binary.LauncherBinary; import pro.gravit.launchserver.binary.LauncherBinary;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.helper.SignHelper; import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader; import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
import pro.gravit.launchserver.manangers.*; import pro.gravit.launchserver.manangers.*;
@ -27,6 +26,7 @@
import pro.gravit.utils.command.CommandHandler; import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand; import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper; import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper; 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 librariesDir;
public final Path controlFile; public final Path controlFile;
public final Path proguardDir; public final Path proguardDir;
/**
* This object contains runtime configuration
*/
public final LaunchServerRuntimeConfig runtime;
/** /**
* Pipeline for building JAR * Pipeline for building JAR
*/ */
@ -105,7 +101,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final ConfigManager configManager; public final ConfigManager configManager;
public final FeaturesManager featuresManager; public final FeaturesManager featuresManager;
public final KeyAgreementManager keyAgreementManager; public final KeyAgreementManager keyAgreementManager;
public final UpdatesManager updatesManager;
// HWID ban + anti-brutforce // HWID ban + anti-brutforce
public final CertificateManager certificateManager; public final CertificateManager certificateManager;
// Server // Server
@ -119,7 +114,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final int shardId; public final int shardId;
public LaunchServerConfig config; 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.dir = directories.dir;
this.tmpDir = directories.tmpDir; this.tmpDir = directories.tmpDir;
this.env = env; this.env = env;
@ -129,7 +124,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.updatesDir = directories.updatesDir; this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager; this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler; this.commandHandler = commandHandler;
this.runtime = runtimeConfig;
this.certificateManager = certificateManager; this.certificateManager = certificateManager;
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread); this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir; launcherLibraries = directories.launcherLibrariesDir;
@ -151,7 +145,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
// Print keypair fingerprints // Print keypair fingerprints
runtime.verify();
config.verify(); config.verify();
// build hooks, anti-brutforce and other // build hooks, anti-brutforce and other
@ -161,7 +154,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
configManager = new ConfigManager(); configManager = new ConfigManager();
featuresManager = new FeaturesManager(this); featuresManager = new FeaturesManager(this);
authManager = new AuthManager(this); authManager = new AuthManager(this);
updatesManager = new UpdatesManager(this);
RestoreResponse.registerProviders(this); RestoreResponse.registerProviders(this);
config.init(ReloadType.FULL); config.init(ReloadType.FULL);
@ -178,7 +170,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
launcherBinary.init(); launcherBinary.init();
launcherEXEBinary.init(); launcherEXEBinary.init();
syncLauncherBinaries();
launcherModuleLoader = new LauncherModuleLoader(this); launcherModuleLoader = new LauncherModuleLoader(this);
if (config.components != null) { if (config.components != null) {
logger.debug("Init components"); logger.debug("Init components");
@ -226,8 +217,15 @@ public void reload(ReloadType type) throws Exception {
if(!type.equals(ReloadType.NO_AUTH)) { if(!type.equals(ReloadType.NO_AUTH)) {
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> { nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
Client client = wsHandler.getClient(); 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); client.auth = config.getAuthProviderPair(client.auth_id);
} finally {
lock.unlock();
} }
}); });
} }
@ -255,7 +253,6 @@ public void invoke(String... args) throws Exception {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
launchServerConfigManager.writeConfig(config); launchServerConfigManager.writeConfig(config);
launchServerConfigManager.writeRuntimeConfig(runtime);
logger.info("LaunchServerConfig saved"); 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() { private LauncherBinary binary() {
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null); LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
modulesManager.invokeEvent(event); modulesManager.invokeEvent(event);
@ -308,8 +317,20 @@ private LauncherBinary binary() {
} }
public void buildLauncherBinaries() throws IOException { public void buildLauncherBinaries() throws IOException {
launcherBinary.build(); PipelineContext launcherContext = launcherBinary.build();
launcherEXEBinary.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 { public void close() throws Exception {
@ -319,17 +340,10 @@ public void close() throws Exception {
// Close handlers & providers // Close handlers & providers
config.close(ReloadType.FULL); config.close(ReloadType.FULL);
modulesManager.invokeEvent(new ClosePhase()); modulesManager.invokeEvent(new ClosePhase());
logger.info("Save LaunchServer runtime config");
launchServerConfigManager.writeRuntimeConfig(runtime);
// Print last message before death :( // Print last message before death :(
logger.info("LaunchServer stopped"); logger.info("LaunchServer stopped");
} }
@Deprecated
public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles();
}
@Deprecated @Deprecated
public void setProfiles(Set<ClientProfile> profilesList) { public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -356,21 +370,6 @@ public void run() {
})); }));
CommonHelper.newThread("Command Thread", true, commandHandler).start(); CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).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) if (config.netty != null)
rebindNettyServerSocket(); 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) { public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) { if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object); reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
@ -454,11 +412,7 @@ public enum LaunchServerEnv {
public interface LaunchServerConfigManager { public interface LaunchServerConfigManager {
LaunchServerConfig readConfig() throws IOException; LaunchServerConfig readConfig() throws IOException;
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
void writeConfig(LaunchServerConfig config) throws IOException; void writeConfig(LaunchServerConfig config) throws IOException;
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
} }
public static class LaunchServerDirectories { public static class LaunchServerDirectories {

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver; package pro.gravit.launchserver;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager; import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.KeyAgreementManager; import pro.gravit.launchserver.manangers.KeyAgreementManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
@ -11,7 +10,6 @@
public class LaunchServerBuilder { public class LaunchServerBuilder {
private LaunchServerConfig config; private LaunchServerConfig config;
private LaunchServerRuntimeConfig runtimeConfig;
private CommandHandler commandHandler; private CommandHandler commandHandler;
private LaunchServer.LaunchServerEnv env; private LaunchServer.LaunchServerEnv env;
private LaunchServerModulesManager modulesManager; private LaunchServerModulesManager modulesManager;
@ -36,11 +34,6 @@ public LaunchServerBuilder setModulesManager(LaunchServerModulesManager modulesM
return this; return this;
} }
public LaunchServerBuilder setRuntimeConfig(LaunchServerRuntimeConfig runtimeConfig) {
this.runtimeConfig = runtimeConfig;
return this;
}
public LaunchServerBuilder setCommandHandler(CommandHandler commandHandler) { public LaunchServerBuilder setCommandHandler(CommandHandler commandHandler) {
this.commandHandler = commandHandler; this.commandHandler = commandHandler;
return this; return this;
@ -77,7 +70,7 @@ public LaunchServer build() throws Exception {
if(shardId == null) { if(shardId == null) {
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0")); 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) { public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
@ -95,19 +88,9 @@ public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override @Override
public void writeConfig(LaunchServerConfig config) { public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException(); 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.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier; 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.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component; import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager; import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager; import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
@ -32,7 +31,6 @@
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -49,7 +47,7 @@ public static void main(String[] args) throws Exception {
LogHelper.printVersion("LaunchServer"); LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer"); LogHelper.printLicense("LaunchServer");
Path dir = IOHelper.WORKING_DIR; Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile; Path configFile;
try { try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
@ -80,7 +78,6 @@ public static void main(String[] args) throws Exception {
} }
} }
LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config; LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION; LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager); LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
@ -94,11 +91,6 @@ public static void main(String[] args) throws Exception {
} else { } else {
configFile = dir.resolve("LaunchServer.json"); 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; CommandHandler localCommandHandler;
try { try {
Class.forName("org.jline.terminal.Terminal"); Class.forName("org.jline.terminal.Terminal");
@ -116,23 +108,12 @@ public static void main(String[] args) throws Exception {
try (BufferedReader reader = IOHelper.newReader(configFile)) { try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class); 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() LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories) .setDirectories(directories)
.setEnv(env) .setEnv(env)
.setCommandHandler(localCommandHandler) .setCommandHandler(localCommandHandler)
.setRuntimeConfig(runtimeConfig)
.setConfig(config) .setConfig(config)
.setModulesManager(modulesManager) .setModulesManager(modulesManager)
.setLaunchServerConfigManager(launchServerConfigManager) .setLaunchServerConfigManager(launchServerConfigManager)
@ -179,7 +160,7 @@ public static void registerAll() {
OptionalAction.registerProviders(); OptionalAction.registerProviders();
OptionalTrigger.registerProviders(); OptionalTrigger.registerProviders();
MixProvider.registerProviders(); MixProvider.registerProviders();
ProfileProvider.registerProviders(); ProfilesProvider.registerProviders();
UpdatesProvider.registerProviders(); UpdatesProvider.registerProviders();
} }
@ -270,11 +251,9 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager { private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
private final Path configFile; private final Path configFile;
private final Path runtimeConfigFile;
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) { public BasicLaunchServerConfigManager(Path configFile) {
this.configFile = configFile; this.configFile = configFile;
this.runtimeConfigFile = runtimeConfigFile;
} }
@Override @Override
@ -286,15 +265,6 @@ public LaunchServerConfig readConfig() throws IOException {
return config1; 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 @Override
public void writeConfig(LaunchServerConfig config) throws IOException { public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
@ -310,21 +280,5 @@ public void writeConfig(LaunchServerConfig config) throws IOException {
IOHelper.write(configFile, bytes); 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("bcrypt", BCryptPasswordVerifier.class);
providers.register("accept", AcceptPasswordVerifier.class); providers.register("accept", AcceptPasswordVerifier.class);
providers.register("reject", RejectPasswordVerifier.class); providers.register("reject", RejectPasswordVerifier.class);
providers.register("django", DjangoPasswordVerifier.class);
registeredProviders = true; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; 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 pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.util.*; public class StdProtectHandler extends ProtectHandler {
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public Map<String, List<String>> profileWhitelist = new HashMap<>();
public List<String> allowUpdates = new ArrayList<>();
@Override @Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) { public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@ -22,38 +15,6 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@Override @Override
public void init(LaunchServer server) { 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir; import pro.gravit.launcher.base.config.JsonConfigurable;
import pro.gravit.launcher.core.serialize.HInput; import pro.gravit.launcher.base.config.SimpleConfigurable;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException; import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*; import java.util.*;
import java.util.stream.Stream;
public class LocalUpdatesProvider extends UpdatesProvider { public class LocalUpdatesProvider extends UpdatesProvider {
private final transient Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public String cacheFile = ".updates-cache";
public String updatesDir = "updates"; public String updatesDir = "updates";
public boolean cacheUpdates = true; public String binaryName = "Launcher";
private volatile transient Map<String, HashedDir> updatesDirMap; public String buildSecretsFile = "build-secrets.json";
public Map<UpdateVariant, String> urls = new HashMap<>(Map.of(
private void writeCache(Path file) throws IOException { UpdateVariant.JAR, "http://localhost:9274/Launcher.jar",
try (HOutput output = new HOutput(IOHelper.newOutput(file))) { UpdateVariant.EXE, "http://localhost:9274/Launcher.exe"
output.writeLength(updatesDirMap.size(), 0); ));
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) { public transient JsonConfigurable<BuildSecretsInfo> buildSecretsJson;
output.writeString(entry.getKey(), 0); private final transient Map<UpdateVariant, byte[]> hashMap = new HashMap<>();
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);
}
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
super.init(server); super.init(server);
buildSecretsJson = new SimpleConfigurable<>(BuildSecretsInfo.class, Path.of(buildSecretsFile));
if(server.env == LaunchServer.LaunchServerEnv.TEST) {
return;
}
try { try {
if (!IOHelper.isDir(Path.of(updatesDir))) buildSecretsJson.generateConfigIfNotExists();
Files.createDirectory(Path.of(updatesDir)); buildSecretsJson.loadConfig();
} catch (Exception e) {
buildSecretsJson.setConfig(buildSecretsJson.getDefaultConfig());
}
try {
sync(UpdateVariant.JAR);
sync(UpdateVariant.EXE);
} catch (IOException e) { } catch (IOException e) {
logger.error("Updates not synced", e); logger.error("Error when syncing binaries", e);
} }
} }
@Override @Override
public void syncInitially() throws IOException { public void pushUpdate(List<UpdateUploadInfo> files) 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<>();
for(var e : files) { 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();
} }
@Override public void sync(UpdateVariant variant) throws IOException {
public void delete(String updateName, List<String> files) throws IOException { var source = getUpdate(variant);
var path = resolveUpdateName(updateName); if(!Files.exists(source)) {
for(var e : files) { logger.warn("Dont exist {} binary", variant);
var target = path.resolve(e); return;
Files.delete(target);
} }
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 @Override
public void delete(String updateName) throws IOException { public UpdateInfo checkUpdates(UpdateVariant variant, BuildSecretsCheck buildSecretsCheck) {
var path = resolveUpdateName(updateName); byte[] hash = hashMap.get(variant);
IOHelper.deleteDir(path, true); 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 static final class BuildSecretsInfo {
public void create(String updateName) throws IOException { private Map<UpdateVariant, BuildSecrets> secrets = new HashMap<>();
var path = resolveUpdateName(updateName);
Files.createDirectories(path); public BuildSecretsInfo(Map<UpdateVariant, BuildSecrets> secrets) {
} this.secrets = secrets;
}
public BuildSecretsInfo() {
}
public Map<UpdateVariant, BuildSecrets> secrets() {
return secrets;
}
}
} }

View file

@ -1,16 +1,19 @@
package pro.gravit.launchserver.auth.updates; package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Arrays;
import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public abstract class UpdatesProvider { public abstract class UpdatesProvider {
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider"); public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
private static boolean registredProviders = false; private static boolean registredProviders = false;
protected transient LaunchServer server; protected transient LaunchServer server;
@ -26,27 +29,37 @@ public void init(LaunchServer server) {
this.server = server; this.server = server;
} }
public void sync() throws IOException { public abstract void pushUpdate(List<UpdateUploadInfo> files) throws IOException;
sync(null); 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 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 abstract class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>(); public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final Path buildDir;
public final String nameFormat;
protected transient final Logger logger = LogManager.getLogger(); 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) { public void addCounted(int count, Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
List<LauncherBuildTask> indexes = new ArrayList<>(); List<LauncherBuildTask> indexes = new ArrayList<>();
tasks.stream().filter(pred).forEach(indexes::add); tasks.stream().filter(pred).forEach(indexes::add);
@ -77,20 +70,4 @@ public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pr
} }
return Optional.empty(); 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.base.Launcher;
import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject; import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.launchserver.asm.InjectClassAcceptor;
import pro.gravit.launchserver.binary.tasks.MainBuildTask; import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
@ -27,9 +28,7 @@
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.HashSet; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -40,16 +39,20 @@
import static pro.gravit.utils.helper.IOHelper.newZipEntry; import static pro.gravit.utils.helper.IOHelper.newZipEntry;
public class BuildContext { public class BuildContext {
public final PipelineContext pipelineContext;
public final ZipOutputStream output; public final ZipOutputStream output;
public final List<JarFile> readerClassPath; public final List<JarFile> readerClassPath;
public final MainBuildTask task; public final MainBuildTask task;
public final HashSet<String> fileList; public final HashSet<String> fileList;
public final HashSet<String> clientModules; public final HashSet<String> clientModules;
public final HashSet<String> legacyClientModules; public final HashSet<String> legacyClientModules;
public final Map<String, Object> properties;
public final List<MainBuildTask.Transformer> transformers = new ArrayList<>();
private Path runtimeDir; private Path runtimeDir;
private boolean deleteRuntimeDir; 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.output = output;
this.readerClassPath = readerClassPath; this.readerClassPath = readerClassPath;
this.task = task; this.task = task;
@ -57,6 +60,9 @@ public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainB
fileList = new HashSet<>(1024); fileList = new HashSet<>(1024);
clientModules = new HashSet<>(); clientModules = new HashSet<>();
legacyClientModules = 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 { public void pushFile(String filename, InputStream inputStream) throws IOException {

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.IOException; import java.io.IOException;
@ -9,14 +10,17 @@
public class EXELauncherBinary extends LauncherBinary { public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) { public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe"); super(server);
} }
@Override @Override
public void build() throws IOException { public UpdatesProvider.UpdateVariant getVariant() {
if (IOHelper.isFile(syncBinaryFile)) { return UpdatesProvider.UpdateVariant.EXE;
Files.delete(syncBinaryFile); }
}
@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.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.*; import pro.gravit.launchserver.binary.tasks.*;
import java.io.IOException; import java.io.IOException;
@ -23,7 +24,7 @@ public final class JARLauncherBinary extends LauncherBinary {
public final Map<String, Path> files; public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException { public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s.jar"); super(server);
count = new AtomicLong(0); count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
buildDir = server.dir.resolve("build"); 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 @Override
public void init() { public void init() {
tasks.add(new PrepareBuildTask(server)); tasks.add(new PrepareBuildTask(server));

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask; import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
@ -12,58 +13,40 @@
public abstract class LauncherBinary extends BinaryPipeline { public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server; public final LaunchServer server;
public final Path syncBinaryFile; public final PipelineContext context;
private volatile byte[] digest;
protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) { protected LauncherBinary(LaunchServer server) {
super(server.tmpDir.resolve("build"), nameFormat);
this.server = server; this.server = server;
syncBinaryFile = binaryFile; this.context = new PipelineContext(server);
} }
public static Path resolve(LaunchServer server, String ext) { public static Path resolve(LaunchServer server, String ext) {
return Path.of(server.config.binaryName + ext); return Path.of(server.config.binaryName + ext);
} }
public void build() throws IOException { public PipelineContext build() throws IOException {
logger.info("Building launcher binary file"); logger.info("Building launcher binary file");
Path thisPath = null;
long time_start = System.currentTimeMillis(); long time_start = System.currentTimeMillis();
long time_this = time_start; long time_this = time_start;
for (LauncherBuildTask task : tasks) { for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName()); logger.info("Task {}", task.getName());
Path oldPath = thisPath; Path newPath = task.process(context);
thisPath = task.process(oldPath); if(newPath != null) {
context.setLastest(newPath);
context.putArtifact(task.getName(), newPath);
}
long time_task_end = System.currentTimeMillis(); long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this; long time_task = time_task_end - time_this;
time_this = time_task_end; time_this = time_task_end;
logger.info("Task {} processed from {} millis", task.getName(), time_task); logger.info("Task {} processed from {} millis", task.getName(), time_task);
} }
long time_end = System.currentTimeMillis(); 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); logger.info("Build successful from {} millis", time_end - time_start);
return this.context;
} }
public final boolean exists() { public abstract UpdatesProvider.UpdateVariant getVariant();
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
}
public final byte[] getDigest() {
return digest;
}
public void init() { 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.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader; import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.SafeClassWriter; import pro.gravit.launchserver.asm.SafeClassWriter;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.IOException; import java.io.IOException;
@ -66,8 +67,9 @@ public String getName() {
} }
@Override @Override
public Path process(Path inputFile) throws IOException { public Path process(PipelineContext context) throws IOException {
Path out = server.launcherBinary.nextPath("post-fixed"); Path inputFile = context.getLastest();
Path out = context.makeTempPath("post-fixed", ".jar");
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) { try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
apply(inputFile, inputFile, output, server, (e) -> false, true); apply(inputFile, inputFile, output, server, (e) -> false, true);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@ protected boolean showApplyDialog(String text) throws IOException {
return response.equals("y"); 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; long total = 0;
for (Downloader.SizedFile file : list) { for (Downloader.SizedFile file : list) {
if(file.size < 0) { if(file.size < 0) {

View file

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

View file

@ -33,10 +33,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
// Register sync commands // Register sync commands
BaseCommandCategory updates = new BaseCommandCategory(); BaseCommandCategory updates = new BaseCommandCategory();
updates.registerCommand("indexAsset", new IndexAssetCommand(server)); //updates.registerCommand("indexAsset", new IndexAssetCommand(server));
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server)); //updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
updates.registerCommand("sync", new SyncCommand(server)); updates.registerCommand("sync", new SyncCommand(server));
updates.registerCommand("profile", new ProfilesCommand(server)); updates.registerCommand("profile", new ProfilesCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management"); 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.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class IndexAssetCommand extends Command { public final class IndexAssetCommand extends Command {
public static final String INDEXES_DIR = "indexes"; public static final String INDEXES_DIR = "indexes";
@ -50,28 +52,25 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 3); /*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]); String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]); String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]); String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); Path inputAssetDir = Path.of(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); Map<String, Path> uploadMap = new HashMap<>();
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);
// Index objects // Index objects
JsonObject objects = new JsonObject(); JsonObject objects = new JsonObject();
logger.info("Indexing objects"); 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 // Write index file
logger.info("Writing asset index file: '{}'", indexFileName); 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(); JsonObject result = new JsonObject();
result.add("objects", objects); result.add("objects", objects);
writer.write(Launcher.gsonManager.gson.toJson(result)); writer.write(Launcher.gsonManager.gson.toJson(result));
@ -79,7 +78,7 @@ public void invoke(String... args) throws Exception {
// Finished // Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully indexed: '{}'", inputAssetDirName); logger.info("Asset successfully indexed: '{}'", inputAssetDirName);*/
} }
public static class IndexObject { public static class IndexObject {
@ -95,12 +94,12 @@ public IndexObject(long size, String hash) {
private final class IndexAssetVisitor extends SimpleFileVisitor<Path> { private final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
private final JsonObject objects; private final JsonObject objects;
private final Path inputAssetDir; 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.objects = objects;
this.inputAssetDir = inputAssetDir; this.inputAssetDir = inputAssetDir;
this.outputAssetDir = outputAssetDir; this.uploadMap = uploadMap;
} }
@Override @Override
@ -112,7 +111,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file)); String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
IndexObject obj = new IndexObject(attrs.size(), digest); IndexObject obj = new IndexObject(attrs.size(), digest);
objects.add(name, Launcher.gsonManager.gson.toJsonTree(obj)); objects.add(name, Launcher.gsonManager.gson.toJsonTree(obj));
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest)); uploadMap.put(resolveObjectFile(Path.of(""), digest).toString(), file);
// Continue visiting // Continue visiting
return super.visitFile(file, attrs); return super.visitFile(file, attrs);

View file

@ -5,6 +5,7 @@
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.CommandException; import pro.gravit.utils.command.CommandException;
@ -14,6 +15,7 @@
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
public final class UnindexAssetCommand extends Command { public final class UnindexAssetCommand extends Command {
@ -35,14 +37,15 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 3); /*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]); String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]); String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]); String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); var updatesDir = server.config.updatesProvider.getUpdatesDir(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); if(updatesDir == null) {
if (outputAssetDir.equals(inputAssetDir)) server.config.updatesProvider.create(inputAssetDirName);
throw new CommandException("Indexed and unindexed asset dirs can't be same"); }
Path outputAssetDir = Path.of(outputAssetDirName);
// Create new asset dir // Create new asset dir
logger.info("Creating unindexed asset dir: '{}'", outputAssetDirName); logger.info("Creating unindexed asset dir: '{}'", outputAssetDirName);
@ -51,7 +54,8 @@ public void invoke(String... args) throws Exception {
// Read JSON file // Read JSON file
JsonObject objects; JsonObject objects;
logger.info("Reading asset index file: '{}'", indexFileName); 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(); objects = JsonParser.parseReader(reader).getAsJsonObject().get("objects").getAsJsonObject();
} }
@ -63,12 +67,12 @@ public void invoke(String... args) throws Exception {
// Copy hashed file to target // Copy hashed file to target
String hash = member.getValue().getAsJsonObject().get("hash").getAsString(); String hash = member.getValue().getAsJsonObject().get("hash").getAsString();
Path source = IndexAssetCommand.resolveObjectFile(inputAssetDir, hash); Path source = IndexAssetCommand.resolveObjectFile(Path.of(""), hash);
IOHelper.copy(source, outputAssetDir.resolve(name)); server.config.updatesProvider.download(inputAssetDirName, Map.of(source.toString(), outputAssetDir.resolve(name)));
} }
// Finished // Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); 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.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder; import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
@ -34,36 +35,14 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 2); verifyArgs(args, 2);
ClientProfile profile; ProfilesProvider.CompletedProfile profile;
try { try {
UUID uuid = UUID.fromString(args[0]); UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid); profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]); profile = server.config.profilesProvider.get(args[0], null);
} }
var builder = new ClientProfileBuilder(profile); server.config.profilesProvider.create(args[1], "Description", 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);
logger.info("Profile {} cloned from {}", args[1], args[0]); 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 org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import java.util.UUID; import java.util.UUID;
@ -27,24 +28,18 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 1); verifyArgs(args, 1);
ClientProfile profile; ProfilesProvider.CompletedProfile profile;
try { try {
UUID uuid = UUID.fromString(args[0]); UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid); profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]); profile = server.config.profilesProvider.get(args[0], null);
} }
if(profile == null) { if(profile == null) {
logger.error("Profile {} not found", args[0]); logger.error("Profile {} not found", args[0]);
return; return;
} }
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir()); logger.info("Delete {}", args[0]);
if(!showApplyDialog("Continue?")) { server.config.profilesProvider.delete(profile);
return;
}
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
server.config.profileProvider.deleteProfile(profile);
logger.info("Delete {}", profile.getDir());
server.config.updatesProvider.delete(profile.getDir());
} }
} }

View file

@ -23,8 +23,8 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) { public void invoke(String... args) {
for(var profile : server.getProfiles()) { for(var profile : server.config.profilesProvider.getProfiles(null)) {
logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : ""); 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 class ProfilesCommand extends Command {
public ProfilesCommand(LaunchServer server) { public ProfilesCommand(LaunchServer server) {
super(server); super(server);
this.childCommands.put("make", new MakeProfileCommand(server)); this.childCommands.put("create", new CreateProfileCommand(server));
this.childCommands.put("save", new SaveProfilesCommand(server));
this.childCommands.put("clone", new CloneProfileCommand(server)); this.childCommands.put("clone", new CloneProfileCommand(server));
this.childCommands.put("list", new ListProfilesCommand(server)); this.childCommands.put("list", new ListProfilesCommand(server));
this.childCommands.put("delete", new DeleteProfileCommand(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); case PROD -> printCheckResult("env", "", true);
} }
//Profiles //Profiles TODO: Implement
for (ClientProfile profile : server.getProfiles()) { /*for (ClientProfile profile : server.config.profilesProvider.getProfiles(null)) {
boolean bad = false; boolean bad = false;
String profileModuleName = "profiles.%s".formatted(profile.getTitle()); String profileModuleName = "profiles.%s".formatted(profile.getTitle());
for (String exc : profile.getUpdateExclusions()) { for (String exc : profile.getUpdateExclusions()) {
@ -177,7 +177,7 @@ public void invoke(String... args) {
} }
if (!bad) if (!bad)
printCheckResult(profileModuleName, "", true); printCheckResult(profileModuleName, "", true);
} }*/
//Linux permissions check //Linux permissions check
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { 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'"); 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"))) { 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"))) { 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'"); 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.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.SubCommand; import pro.gravit.utils.command.SubCommand;
@ -28,9 +29,9 @@ public void invoke(String... args) throws Exception {
public void invoke(String... args) { public void invoke(String... args) {
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair(); AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]); boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]);
ClientProfile profile = null; ProfilesProvider.UncompletedProfile profile = null;
for (ClientProfile p : server.getProfiles()) { for (var p : server.config.profilesProvider.getProfiles(null)) {
if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) { if (p.getName().equals(args[0]) || p.getUuid().toString().equals(args[0])) {
profile = p; profile = p;
break; break;
} }
@ -42,7 +43,7 @@ public void invoke(String... args) {
logger.error("AuthId {} not found", args[1]); logger.error("AuthId {} not found", args[1]);
return; 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); 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 class SyncCommand extends Command {
public SyncCommand(LaunchServer server) { public SyncCommand(LaunchServer server) {
super(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("launchermodules", new SyncLauncherModulesCommand(server));
this.childCommands.put("updatescache", new SyncUpdatesCacheCommand(server));
} }
@Override @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 org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable; import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.binary.PipelineContext;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask; import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.command.Command; import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand; import pro.gravit.utils.command.SubCommand;
@ -137,19 +138,18 @@ public String getName() {
} }
@Override @Override
public Path process(Path inputFile) throws IOException { public Path process(PipelineContext context) throws IOException {
if (!component.enabled) { if (!component.enabled) {
return inputFile; return null;
} }
LauncherBuildTask task = server.launcherBinary.getTaskBefore((x) -> proguardTaskName.equals(x.getName())).get(); Path lastPath = context.getLastest();
Path lastPath = server.launcherBinary.nextPath(task);
if(Files.notExists(lastPath)) { if(Files.notExists(lastPath)) {
logger.error("{} not exist. Multi-Release JAR fix not applied!", 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(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(); ZipEntry entry = input.getNextEntry();
while(entry != null) { while(entry != null) {
ZipEntry newEntry = new ZipEntry(entry.getName()); ZipEntry newEntry = new ZipEntry(entry.getName());
@ -193,8 +193,8 @@ public String getName() {
} }
@Override @Override
public Path process(Path inputFile) throws IOException { public Path process(PipelineContext context) throws IOException {
Path outputJar = server.launcherBinary.nextLowerPath(this); Path outputJar = context.makeTempPath("proguard", "jar");
if (component.enabled) { if (component.enabled) {
if (!checkJMods(IOHelper.JVM_DIR.resolve("jmods"))) { if (!checkJMods(IOHelper.JVM_DIR.resolve("jmods"))) {
throw new RuntimeException("Java path: %s is not JDK! Please install JDK".formatted(IOHelper.JVM_DIR)); 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"); 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() Process process = new ProcessBuilder()
.command(args) .command(args)
@ -240,8 +240,9 @@ public Path process(Path inputFile) throws IOException {
} catch (Exception e) { } catch (Exception e) {
logger.error(e); logger.error(e);
} }
} else } else {
IOHelper.copy(inputFile, outputJar); return null;
}
return outputJar; return outputJar;
} }
} }

View file

@ -8,8 +8,8 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider; import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.auth.profiles.LocalProfileProvider; import pro.gravit.launchserver.auth.profiles.LocalProfilesProvider;
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.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.StdProtectHandler; import pro.gravit.launchserver.auth.protect.StdProtectHandler;
import pro.gravit.launchserver.auth.texture.RequestTextureProvider; import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
@ -28,7 +28,7 @@
public final class LaunchServerConfig { 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/", 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.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(); private transient final Logger logger = LogManager.getLogger();
public String projectName; public String projectName;
public String[] mirrors; public String[] mirrors;
@ -39,7 +39,7 @@ public final class LaunchServerConfig {
// Handlers & Providers // Handlers & Providers
public ProtectHandler protectHandler; public ProtectHandler protectHandler;
public Map<String, Component> components; public Map<String, Component> components;
public ProfileProvider profileProvider = new LocalProfileProvider(); public ProfilesProvider profilesProvider = new LocalProfilesProvider();
public UpdatesProvider updatesProvider = new LocalUpdatesProvider(); public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
public NettyConfig netty; public NettyConfig netty;
public LauncherConf launcher; public LauncherConf launcher;
@ -49,7 +49,7 @@ public final class LaunchServerConfig {
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
LaunchServerConfig newConfig = new LaunchServerConfig(); 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.env = LauncherConfig.LauncherEnvironment.STD;
newConfig.auth = new HashMap<>(); newConfig.auth = new HashMap<>();
AuthProviderPair a = new AuthProviderPair(new RejectAuthCoreProvider(), AuthProviderPair a = new AuthProviderPair(new RejectAuthCoreProvider(),
@ -85,7 +85,8 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
newConfig.components.put("authLimiter", authLimiterComponent); newConfig.components.put("authLimiter", authLimiterComponent);
ProGuardComponent proGuardComponent = new ProGuardComponent(); ProGuardComponent proGuardComponent = new ProGuardComponent();
newConfig.components.put("proguard", proGuardComponent); newConfig.components.put("proguard", proGuardComponent);
newConfig.profileProvider = new LocalProfileProvider(); newConfig.profilesProvider = new LocalProfilesProvider();
newConfig.updatesProvider = new LocalUpdatesProvider();
return newConfig; return newConfig;
} }
@ -151,7 +152,7 @@ public void verify() {
for (int i = 0; i < mirrors.length; ++i) { for (int i = 0; i < mirrors.length; ++i) {
if (mirrors[i] != null && oldMirrorList.contains(mirrors[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]); 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); server.registerObject("protectHandler", protectHandler);
protectHandler.init(server); protectHandler.init(server);
} }
if(profileProvider != null) { if(profilesProvider != null) {
server.registerObject("profileProvider", profileProvider); server.registerObject("profileProvider", profilesProvider);
profileProvider.init(server); profilesProvider.init(server);
} }
if(updatesProvider != null) { if(updatesProvider != null) {
server.registerObject("updatesProvider", updatesProvider); server.registerObject("updatesProvider", updatesProvider);
@ -215,9 +216,9 @@ public void close(LaunchServer.ReloadType type) {
server.unregisterObject("protectHandler", protectHandler); server.unregisterObject("protectHandler", protectHandler);
protectHandler.close(); protectHandler.close();
} }
if(profileProvider != null) { if(profilesProvider != null) {
server.unregisterObject("profileProvider", profileProvider); server.unregisterObject("profilesProvider", profilesProvider);
profileProvider.close(); profilesProvider.close();
} }
if(updatesProvider != null) { if(updatesProvider != null) {
server.unregisterObject("updatesProvider", updatesProvider); 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.setMinJavaVersion(21);
builder.setRecommendJavaVersion(17); builder.setRecommendJavaVersion(21);
if(version.compareTo(ClientProfileVersions.MINECRAFT_1_20_3) >= 0) {
builder.setMinJavaVersion(21);
builder.setRecommendJavaVersion(21);
}
jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true"); jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true");
jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
builder.setJvmArgs(jvmArgs); builder.setJvmArgs(jvmArgs);

View file

@ -47,7 +47,7 @@ public void init() {
MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get(); MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get();
mainTask.preBuildHook.registerHook((buildContext) -> { mainTask.preBuildHook.registerHook((buildContext) -> {
for (ModuleEntity e : launcherModules) { 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) { if(e.modernModule) {
buildContext.clientModules.add(e.moduleMainClass); buildContext.clientModules.add(e.moduleMainClass);
} else { } else {

View file

@ -191,7 +191,7 @@ public PlayerProfile getPlayerProfile(Client client) {
playerProfile = getPlayerProfile(client.auth, user); playerProfile = getPlayerProfile(client.auth, user);
if (playerProfile != null) return playerProfile; if (playerProfile != null) return playerProfile;
if (client.auth.textureProvider != null) { 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 combined profile
return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>()); 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 { 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) { if (password instanceof AuthRSAPassword authRSAPassword) {
try { try {
Cipher cipher = SecurityHelper.newRSADecryptCipher(server.keyAgreementManager.rsaPrivateKey); 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.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier; 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.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider; 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(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers));
builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers)); builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers));
builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.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)); builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
modulesManager.invokeEvent(new PreGsonPhase(builder)); modulesManager.invokeEvent(new PreGsonPhase(builder));
//ClientWebSocketService.appendTypeAdapters(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.LauncherInitContext;
import pro.gravit.launcher.base.modules.LauncherModule; 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.launcher.base.modules.events.InitPhase;
import pro.gravit.utils.Version; import pro.gravit.utils.Version;
public class LaunchServerCoreModule extends LauncherModule { public class LaunchServerCoreModule extends LauncherModule {
public LaunchServerCoreModule() { public LaunchServerCoreModule() {
super(new LauncherModuleInfo("LaunchServerCore", Version.getVersion())); super(new LauncherModuleInfoBuilder().setName("LaunchServerCore").setVersion(Version.getVersion()).createLauncherModuleInfo());
} }
@Override @Override

View file

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

View file

@ -3,6 +3,7 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.CurrentUserRequestEvent; import pro.gravit.launcher.base.events.request.CurrentUserRequestEvent;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -12,6 +13,13 @@ public static CurrentUserRequestEvent.UserInfo collectUserInfoFromClient(LaunchS
CurrentUserRequestEvent.UserInfo result = new CurrentUserRequestEvent.UserInfo(); CurrentUserRequestEvent.UserInfo result = new CurrentUserRequestEvent.UserInfo();
if (client.auth != null && client.isAuth && client.username != null) { if (client.auth != null && client.isAuth && client.username != null) {
result.playerProfile = server.authManager.getPlayerProfile(client); 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; result.permissions = client.permissions;
return result; return result;

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.UpdateRequestEvent; import pro.gravit.launcher.base.events.request.UpdateRequestEvent;
import pro.gravit.launcher.core.hasher.HashedDir; 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.config.LaunchServerConfig;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -19,15 +18,22 @@ public String getType() {
@Override @Override
public void execute(ChannelHandlerContext ctx, Client client) { 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) { if (dirName == null) {
sendError("Invalid request"); sendError("Invalid request");
return; 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) { if (dir == null) {
sendError("Directory %s not found".formatted(dirName)); sendError("Directory %s not found".formatted(dirName));
return; return;

View file

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

View file

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

View file

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

View file

@ -2,16 +2,12 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
public class TestLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager { public class TestLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
public LaunchServerConfig config; public LaunchServerConfig config;
public LaunchServerRuntimeConfig runtimeConfig;
public TestLaunchServerConfigManager() { public TestLaunchServerConfigManager() {
config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST); config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
runtimeConfig = new LaunchServerRuntimeConfig();
runtimeConfig.reset();
} }
@Override @Override
@ -19,18 +15,8 @@ public LaunchServerConfig readConfig() {
return config; return config;
} }
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
return runtimeConfig;
}
@Override @Override
public void writeConfig(LaunchServerConfig config) { 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" String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
@ -7,8 +7,8 @@
url "https://repo.spring.io/plugins-release/" url "https://repo.spring.io/plugins-release/"
} }
} }
sourceCompatibility = '17' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
configurations { configurations {
bundle bundle

View file

@ -2,14 +2,18 @@
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig; import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launcher.base.request.*;
import pro.gravit.launcher.client.*; 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.*;
import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase; import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase;
import pro.gravit.launcher.client.events.ClientExitPhase; import pro.gravit.launcher.client.events.ClientExitPhase;
import pro.gravit.launcher.runtime.client.events.ClientPreGuiPhase; import pro.gravit.launcher.runtime.client.events.ClientPreGuiPhase;
import pro.gravit.launcher.runtime.console.GetPublicKeyCommand;
import pro.gravit.launcher.runtime.console.ModulesCommand; import pro.gravit.launcher.runtime.console.ModulesCommand;
import pro.gravit.launcher.runtime.console.SignDataCommand;
import pro.gravit.launcher.runtime.gui.NoRuntimeProvider; import pro.gravit.launcher.runtime.gui.NoRuntimeProvider;
import pro.gravit.launcher.runtime.gui.RuntimeProvider; import pro.gravit.launcher.runtime.gui.RuntimeProvider;
import pro.gravit.launcher.runtime.managers.ConsoleManager; import pro.gravit.launcher.runtime.managers.ConsoleManager;
@ -19,25 +23,16 @@
import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.base.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; 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.auth.*;
import pro.gravit.launcher.base.request.websockets.OfflineRequestService; import pro.gravit.launcher.base.request.websockets.OfflineRequestService;
import pro.gravit.launcher.base.request.websockets.StdWebSocketService; import pro.gravit.launcher.base.request.websockets.StdWebSocketService;
import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.start.RuntimeModuleManager;
import pro.gravit.utils.helper.*; import pro.gravit.utils.helper.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -48,8 +43,6 @@ public class LauncherEngine {
// Instance // Instance
private final AtomicBoolean started = new AtomicBoolean(false); private final AtomicBoolean started = new AtomicBoolean(false);
public RuntimeProvider runtimeProvider; public RuntimeProvider runtimeProvider;
public ECPublicKey publicKey;
public ECPrivateKey privateKey;
public Class<? extends RuntimeProvider> basicRuntimeProvider; public Class<? extends RuntimeProvider> basicRuntimeProvider;
private LauncherEngine(boolean clientInstance, Class<? extends RuntimeProvider> basicRuntimeProvider) { private LauncherEngine(boolean clientInstance, Class<? extends RuntimeProvider> basicRuntimeProvider) {
@ -176,37 +169,9 @@ public static LauncherEngine newInstance(boolean clientInstance, Class<? extends
return new LauncherEngine(clientInstance, basicRuntimeProvider); return new LauncherEngine(clientInstance, basicRuntimeProvider);
} }
public ECPublicKey getClientPublicKey() {
return publicKey;
}
public byte[] sign(byte[] bytes) {
return SecurityHelper.sign(bytes, privateKey);
}
public void readKeys() throws IOException, InvalidKeySpecException {
if (privateKey != null || publicKey != null) return;
Path dir = DirBridge.dir;
Path publicKeyFile = dir.resolve("public.key");
Path privateKeyFile = dir.resolve("private.key");
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
LogHelper.info("Reading EC keypair");
publicKey = SecurityHelper.toPublicECDSAKey(IOHelper.read(publicKeyFile));
privateKey = SecurityHelper.toPrivateECDSAKey(IOHelper.read(privateKeyFile));
} else {
LogHelper.info("Generating EC keypair");
KeyPair pair = SecurityHelper.genECDSAKeyPair(new SecureRandom());
publicKey = (ECPublicKey) pair.getPublic();
privateKey = (ECPrivateKey) pair.getPrivate();
// Write key pair list
LogHelper.info("Writing EC keypair list");
IOHelper.write(publicKeyFile, publicKey.getEncoded());
IOHelper.write(privateKeyFile, privateKey.getEncoded());
}
}
public void start(String... args) throws Throwable { public void start(String... args) throws Throwable {
var config = Launcher.getConfig();
config.apply();
//Launcher.modulesManager = new ClientModuleManager(this); //Launcher.modulesManager = new ClientModuleManager(this);
ClientPreGuiPhase event = new ClientPreGuiPhase(null); ClientPreGuiPhase event = new ClientPreGuiPhase(null);
LauncherEngine.modulesManager.invokeEvent(event); LauncherEngine.modulesManager.invokeEvent(event);
@ -215,7 +180,7 @@ public void start(String... args) throws Throwable {
runtimeProvider.init(clientInstance); runtimeProvider.init(clientInstance);
//runtimeProvider.preLoad(); //runtimeProvider.preLoad();
if (!Request.isAvailable()) { if (!Request.isAvailable()) {
String address = Launcher.getConfig().address; String address = config.address;
LogHelper.debug("Start async connection to %s", address); LogHelper.debug("Start async connection to %s", address);
RequestService service; RequestService service;
try { try {
@ -243,10 +208,22 @@ public void start(String... args) throws Throwable {
} }
Request.startAutoRefresh(); Request.startAutoRefresh();
Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler()); 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"); Objects.requireNonNull(args, "args");
if (started.getAndSet(true)) if (started.getAndSet(true))
throw new IllegalStateException("Launcher has been already started"); throw new IllegalStateException("Launcher has been already started");
readKeys();
registerCommands(); registerCommands();
LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this)); LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this));
runtimeProvider.preLoad(); runtimeProvider.preLoad();
@ -255,8 +232,6 @@ public void start(String... args) throws Throwable {
} }
private void registerCommands() { private void registerCommands() {
ConsoleManager.handler.registerCommand("getpublickey", new GetPublicKeyCommand(this));
ConsoleManager.handler.registerCommand("signdata", new SignDataCommand(this));
ConsoleManager.handler.registerCommand("modules", new ModulesCommand()); ConsoleManager.handler.registerCommand("modules", new ModulesCommand());
} }
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launcher.runtime; 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 pro.gravit.launcher.core.LauncherNetworkAPI;
import java.util.HashMap; 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.Launcher;
import pro.gravit.launcher.base.LauncherConfig; 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.ClientLauncherEntryPoint;
import pro.gravit.launcher.client.ClientParams; import pro.gravit.launcher.client.ClientParams;
import pro.gravit.launcher.runtime.LauncherEngine; 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, public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken, ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) { 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.javaVersion = javaVersion;
this.workDir = clientDir.toAbsolutePath(); this.workDir = clientDir.toAbsolutePath();
this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir); 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.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
this.params.assetDir = assetDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString();
this.params.timestamp = System.currentTimeMillis(); this.params.timestamp = System.currentTimeMillis();
this.params.oauth = oAuthRequestEvent;
this.params.authId = authId;
Path nativesPath; Path nativesPath;
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) { if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
nativesPath = workDir.resolve("natives"); nativesPath = workDir.resolve("natives");
@ -119,10 +128,8 @@ private void applyClientProfile() {
if (params.ram > 0) { if (params.ram > 0) {
this.jvmArgs.add("-Xmx" + params.ram + 'M'); this.jvmArgs.add("-Xmx" + params.ram + 'M');
} }
this.params.oauth = Request.getOAuth();
if (this.params.oauth == null) { if (this.params.oauth == null) {
throw new UnsupportedOperationException("Legacy session not supported"); this.params.oauth = Request.getOAuth();
} else {
this.params.authId = Request.getAuthId(); this.params.authId = Request.getAuthId();
this.params.oauthExpiredTime = Request.getTokenExpiredTime(); this.params.oauthExpiredTime = Request.getTokenExpiredTime();
this.params.extendedTokens = Request.getExtendedTokens(); this.params.extendedTokens = Request.getExtendedTokens();

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.runtime.client; package pro.gravit.launcher.runtime.client;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import pro.gravit.launcher.core.backend.UserSettings;
import pro.gravit.launcher.start.RuntimeModuleManager; import pro.gravit.launcher.start.RuntimeModuleManager;
import pro.gravit.launcher.core.managers.GsonManager; import pro.gravit.launcher.core.managers.GsonManager;
import pro.gravit.launcher.base.modules.events.PreGsonPhase; import pro.gravit.launcher.base.modules.events.PreGsonPhase;

View file

@ -5,6 +5,7 @@
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileVersions; 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.HInput;
import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
@ -18,6 +19,7 @@
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; 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; public final int onlinePlayers;
@ -228,5 +230,20 @@ public Result(int onlinePlayers, int maxPlayers, String raw) {
public boolean isOverfilled() { public boolean isOverfilled() {
return onlinePlayers >= maxPlayers; 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; config.unlockSecret = DebugProperties.UNLOCK_SECRET;
Launcher.setConfig(config); Launcher.setConfig(config);
Launcher.applyLauncherEnv(DebugProperties.ENV); Launcher.applyLauncherEnv(DebugProperties.ENV);
config.apply();
LauncherEngine.modulesManager = new RuntimeModuleManager(); LauncherEngine.modulesManager = new RuntimeModuleManager();
LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule()); LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule());
for (String moduleClassName : DebugProperties.MODULE_CLASSES) { 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.hardware.*;
import oshi.software.os.OperatingSystem; import oshi.software.os.OperatingSystem;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest; import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
import pro.gravit.utils.helper.JVMHelper;
import java.util.List; import java.util.List;
@ -104,6 +106,27 @@ public String getBaseboardSerialNumber() {
return hardware.getComputerSystem().getBaseboard().getSerialNumber(); return hardware.getComputerSystem().getBaseboard().getSerialNumber();
} }
public HardwareVerificationFeatureAPI.HardwareStatisticData getStatisticData() {
return new HardwareVerificationFeatureAPI.HardwareStatisticData(
JVMHelper.ARCH.toHardwareFeatureArch(JVMHelper.ARCH_TYPE),
JVMHelper.OS.toHardwareFeatureOs(JVMHelper.OS_TYPE),
getTotalMemory(),
getProcessorLogicalCount(),
getProcessorPhysicalCount(),
getProcessorMaxFreq(),
isBattery(),
getGraphicCardName()
);
}
public HardwareVerificationFeatureAPI.HardwareIdentifyData getIdentifyData() {
return new HardwareVerificationFeatureAPI.HardwareIdentifyData(
getBaseboardSerialNumber(),
getHWDiskID(),
getDisplayID()
);
}
public HardwareReportRequest.HardwareInfo getHardwareInfo(boolean needSerial) { public HardwareReportRequest.HardwareInfo getHardwareInfo(boolean needSerial) {
HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo(); HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo();
info.bitness = getBitness(); info.bitness = getBitness();

View file

@ -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' sourceCompatibility = '21'
targetCompatibility = '17' targetCompatibility = '21'
dependencies { dependencies {
api project(':LauncherCore') api project(':LauncherCore')

View file

@ -1,10 +1,11 @@
package pro.gravit.launcher.base; package pro.gravit.launcher.base;
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.model.UserPermissions;
import java.util.*; import java.util.*;
public class ClientPermissions { public class ClientPermissions implements UserPermissions {
public static final ClientPermissions DEFAULT = new ClientPermissions(); public static final ClientPermissions DEFAULT = new ClientPermissions();
@LauncherNetworkAPI @LauncherNetworkAPI
private List<String> roles; private List<String> roles;
@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() {
return perm; return perm;
} }
@Override
public boolean hasRole(String role) { public boolean hasRole(String role) {
return roles != null && roles.contains(role); return roles != null && roles.contains(role);
} }
@ -46,6 +48,7 @@ public synchronized void compile() {
} }
} }
@Override
public boolean hasPerm(String action) { public boolean hasPerm(String action) {
if (available == null) { if (available == null) {
compile(); compile();

View file

@ -234,7 +234,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do
return task.get(); return task.get();
} }
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException { public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException {
URI uri; URI uri;
if(baseUri != null) { if(baseUri != null) {
String scheme = baseUri.getScheme(); String scheme = baseUri.getScheme();
@ -247,6 +247,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy
} else { } else {
uri = new URI(filePath); uri = new URI(filePath);
} }
return uri;
}
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
var uri = makeURI(baseUri, filePath);
return HttpRequest.newBuilder() return HttpRequest.newBuilder()
.GET() .GET()
.uri(uri) .uri(uri)

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.base; package pro.gravit.launcher.base;
import pro.gravit.launcher.core.BuildInParams;
import pro.gravit.launcher.core.LauncherInject; import pro.gravit.launcher.core.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor; import pro.gravit.launcher.core.LauncherInjectionConstructor;
import pro.gravit.launcher.core.LauncherTrustManager; import pro.gravit.launcher.core.LauncherTrustManager;
@ -8,6 +9,7 @@
import pro.gravit.launcher.core.serialize.HInput; import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject; import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
@ -122,6 +124,12 @@ public LauncherConfig(String address, Map<String, byte[]> runtime, String projec
runtimeEncryptKey = null; 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) { public static void initModules(LauncherModulesManager modulesManager) {
if(JVMHelper.JVM_VERSION >= 17) { if(JVMHelper.JVM_VERSION >= 17) {
modulesClasses.addAll(ModernModulesClass.modulesClasses); modulesClasses.addAll(ModernModulesClass.modulesClasses);

View file

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

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.events.request; package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.LauncherNetworkAPI; import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.profiles.PlayerProfile;
@ -67,7 +68,15 @@ public String getType() {
return "auth"; 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 accessToken;
public final String refreshToken; public final String refreshToken;
public final long expire; public final long expire;
@ -77,5 +86,26 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
this.refreshToken = refreshToken; this.refreshToken = refreshToken;
this.expire = expire; this.expire = expire;
} }
public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) {
this.accessToken = token.getAccessToken();
this.refreshToken = token.getRefreshToken();
this.expire = token.getExpire();
}
@Override
public String getAccessToken() {
return accessToken;
}
@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.ClientPermissions;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile; 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 class CurrentUserRequestEvent extends RequestEvent {
public final UserInfo userInfo; public final UserInfo userInfo;
@ -16,7 +21,7 @@ public String getType() {
return "currentUser"; return "currentUser";
} }
public static class UserInfo { public static class UserInfo implements SelfUser {
public ClientPermissions permissions; public ClientPermissions permissions;
public String accessToken; public String accessToken;
public PlayerProfile playerProfile; public PlayerProfile playerProfile;
@ -29,5 +34,35 @@ public UserInfo(ClientPermissions permissions, String accessToken, PlayerProfile
this.accessToken = accessToken; this.accessToken = accessToken;
this.playerProfile = playerProfile; 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; 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.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent; 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 pro.gravit.utils.TypeSerializeInterface;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; 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; public final List<AuthAvailabilityDetails> details;
@LauncherNetworkAPI @LauncherNetworkAPI
public String name; public String name;
@ -59,5 +67,34 @@ public AuthAvailability(List<AuthAvailabilityDetails> details, String name, Stri
this.visible = visible; this.visible = visible;
this.features = features; 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; package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
public class GetSecureLevelInfoRequestEvent extends RequestEvent { public class GetSecureLevelInfoRequestEvent extends RequestEvent implements HardwareVerificationFeatureAPI.SecurityLevelInfo {
public final byte[] verifySecureKey; public final byte[] verifySecureKey;
public boolean enabled; public boolean enabled;
@ -19,4 +20,14 @@ public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey, boolean enabled) {
public String getType() { public String getType() {
return "getSecureLevelInfo"; return "getSecureLevelInfo";
} }
@Override
public boolean isRequired() {
return enabled;
}
@Override
public byte[] getSignData() {
return verifySecureKey;
}
} }

View file

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

View file

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

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