mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-07-07 00:09:46 +03:00
Compare commits
60 commits
5961777b52
...
bcdd43fef6
Author | SHA1 | Date | |
---|---|---|---|
|
bcdd43fef6 | ||
|
b1929f7927 | ||
|
c46503fa95 | ||
|
8421da7772 | ||
|
26c21a3ec7 | ||
|
bd0423460f | ||
|
846ab77795 | ||
|
bda6a35945 | ||
|
101d8c7275 | ||
|
b149686abd | ||
|
38dcc1a53e | ||
|
324de7226d | ||
|
37546e8a5a | ||
|
110c2a089e | ||
|
6013f75091 | ||
|
04ecbfc14b | ||
|
68e2230d34 | ||
|
183d0fc9df | ||
|
e1ee1099cc | ||
|
b0e840a040 | ||
|
0de8ff6e14 | ||
|
b9c6472e2a | ||
|
230fd22f99 | ||
|
d2a8bc2c35 | ||
|
ab1fa64f7a | ||
|
a959414c64 | ||
|
2d046f7d7b | ||
|
15ed66b3d5 | ||
|
cacbbfc8ed | ||
|
0a163bb09c | ||
|
f246ab697b | ||
|
a03de56dde | ||
|
2470780591 | ||
|
31f919021c | ||
|
463d381180 | ||
|
03b7f35418 | ||
|
6110a29f5c | ||
|
88a70bf47b | ||
|
88fa3ca1a0 | ||
|
c65e20d77b | ||
|
e664c579a5 | ||
|
1e4e1c5837 | ||
|
b8f6857ef2 | ||
|
de844201b4 | ||
|
27bcfc046e | ||
|
f946c893e1 | ||
|
f1559183a1 | ||
|
c90a13af71 | ||
|
f297a5878d | ||
|
05530b6664 | ||
|
d4d6491e52 | ||
|
83cc441574 | ||
|
6b873b5072 | ||
|
44e840734c | ||
|
3afe77bf34 | ||
|
f6b3a62497 | ||
|
8f20cbe104 | ||
|
66d8b9d9ca | ||
|
90ee90973e | ||
|
8bf58cff18 |
193 changed files with 4783 additions and 1693 deletions
|
@ -129,7 +129,7 @@
|
|||
|
||||
tasks.register('dumpProguard', Copy) {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
into "$buildDir/libs/proguard"
|
||||
into "$buildDir/libs/proguard-libraries"
|
||||
from configurations.proguardPack
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
|
||||
import pro.gravit.launcher.base.modules.events.ClosePhase;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.launchserver.binary.EXELauncherBinary;
|
||||
import pro.gravit.launchserver.binary.JARLauncherBinary;
|
||||
import pro.gravit.launchserver.binary.LauncherBinary;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
import pro.gravit.launchserver.helper.SignHelper;
|
||||
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
|
||||
import pro.gravit.launchserver.manangers.*;
|
||||
|
@ -27,6 +26,7 @@
|
|||
import pro.gravit.utils.command.CommandHandler;
|
||||
import pro.gravit.utils.command.SubCommand;
|
||||
import pro.gravit.utils.helper.CommonHelper;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
|
@ -83,10 +83,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
|||
public final Path librariesDir;
|
||||
public final Path controlFile;
|
||||
public final Path proguardDir;
|
||||
/**
|
||||
* This object contains runtime configuration
|
||||
*/
|
||||
public final LaunchServerRuntimeConfig runtime;
|
||||
/**
|
||||
* Pipeline for building JAR
|
||||
*/
|
||||
|
@ -105,7 +101,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
|||
public final ConfigManager configManager;
|
||||
public final FeaturesManager featuresManager;
|
||||
public final KeyAgreementManager keyAgreementManager;
|
||||
public final UpdatesManager updatesManager;
|
||||
// HWID ban + anti-brutforce
|
||||
public final CertificateManager certificateManager;
|
||||
// Server
|
||||
|
@ -119,7 +114,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
|||
public final int shardId;
|
||||
public LaunchServerConfig config;
|
||||
|
||||
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
|
||||
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
|
||||
this.dir = directories.dir;
|
||||
this.tmpDir = directories.tmpDir;
|
||||
this.env = env;
|
||||
|
@ -129,7 +124,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
this.updatesDir = directories.updatesDir;
|
||||
this.keyAgreementManager = keyAgreementManager;
|
||||
this.commandHandler = commandHandler;
|
||||
this.runtime = runtimeConfig;
|
||||
this.certificateManager = certificateManager;
|
||||
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
|
||||
launcherLibraries = directories.launcherLibrariesDir;
|
||||
|
@ -151,7 +145,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
|
||||
// Print keypair fingerprints
|
||||
|
||||
runtime.verify();
|
||||
config.verify();
|
||||
|
||||
// build hooks, anti-brutforce and other
|
||||
|
@ -161,7 +154,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
configManager = new ConfigManager();
|
||||
featuresManager = new FeaturesManager(this);
|
||||
authManager = new AuthManager(this);
|
||||
updatesManager = new UpdatesManager(this);
|
||||
RestoreResponse.registerProviders(this);
|
||||
|
||||
config.init(ReloadType.FULL);
|
||||
|
@ -178,7 +170,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
|
||||
launcherBinary.init();
|
||||
launcherEXEBinary.init();
|
||||
syncLauncherBinaries();
|
||||
launcherModuleLoader = new LauncherModuleLoader(this);
|
||||
if (config.components != null) {
|
||||
logger.debug("Init components");
|
||||
|
@ -226,8 +217,15 @@ public void reload(ReloadType type) throws Exception {
|
|||
if(!type.equals(ReloadType.NO_AUTH)) {
|
||||
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
|
||||
Client client = wsHandler.getClient();
|
||||
if(client.auth != null) {
|
||||
var lock = client.writeLock();
|
||||
lock.lock();
|
||||
try {
|
||||
if (client.auth_id == null) {
|
||||
return;
|
||||
}
|
||||
client.auth = config.getAuthProviderPair(client.auth_id);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -255,7 +253,6 @@ public void invoke(String... args) throws Exception {
|
|||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
launchServerConfigManager.writeConfig(config);
|
||||
launchServerConfigManager.writeRuntimeConfig(runtime);
|
||||
logger.info("LaunchServerConfig saved");
|
||||
}
|
||||
};
|
||||
|
@ -298,6 +295,18 @@ public void checkCertificateExpired() {
|
|||
}
|
||||
}
|
||||
|
||||
public Path createTempDirectory(String name) throws IOException {
|
||||
var path = tmpDir.resolve(String.format("launchserver-%s-%s", name, SecurityHelper.randomStringToken()));
|
||||
Files.createDirectories(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public Path createTempFilePath(String name, String ext) throws IOException {
|
||||
var path = tmpDir.resolve(String.format("launchserver-%s-%s.%s", name, SecurityHelper.randomStringToken(), ext));
|
||||
IOHelper.createParentDirs(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private LauncherBinary binary() {
|
||||
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
|
||||
modulesManager.invokeEvent(event);
|
||||
|
@ -308,8 +317,20 @@ private LauncherBinary binary() {
|
|||
}
|
||||
|
||||
public void buildLauncherBinaries() throws IOException {
|
||||
launcherBinary.build();
|
||||
launcherEXEBinary.build();
|
||||
PipelineContext launcherContext = launcherBinary.build();
|
||||
PipelineContext exeContext = launcherEXEBinary.build();
|
||||
UpdatesProvider.UpdateUploadInfo jarInfo = launcherContext.makeUploadInfo(UpdatesProvider.UpdateVariant.JAR);
|
||||
UpdatesProvider.UpdateUploadInfo exeInfo = exeContext.makeUploadInfo(UpdatesProvider.UpdateVariant.EXE);
|
||||
List<UpdatesProvider.UpdateUploadInfo> list = new ArrayList<>(2);
|
||||
if(jarInfo != null) {
|
||||
list.add(jarInfo);
|
||||
}
|
||||
if(exeInfo != null) {
|
||||
list.add(exeInfo);
|
||||
}
|
||||
config.updatesProvider.pushUpdate(list);
|
||||
launcherContext.clear();
|
||||
exeContext.clear();
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
|
@ -319,17 +340,10 @@ public void close() throws Exception {
|
|||
// Close handlers & providers
|
||||
config.close(ReloadType.FULL);
|
||||
modulesManager.invokeEvent(new ClosePhase());
|
||||
logger.info("Save LaunchServer runtime config");
|
||||
launchServerConfigManager.writeRuntimeConfig(runtime);
|
||||
// Print last message before death :(
|
||||
logger.info("LaunchServer stopped");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<ClientProfile> getProfiles() {
|
||||
return config.profileProvider.getProfiles();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setProfiles(Set<ClientProfile> profilesList) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
@ -356,21 +370,6 @@ public void run() {
|
|||
}));
|
||||
CommonHelper.newThread("Command Thread", true, commandHandler).start();
|
||||
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
|
||||
// Sync updates dir
|
||||
CommonHelper.newThread("Profiles and updates sync", true, () -> {
|
||||
try {
|
||||
// Sync profiles dir
|
||||
syncProfilesDir();
|
||||
|
||||
// Sync updates dir
|
||||
config.updatesProvider.syncInitially();
|
||||
|
||||
|
||||
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
|
||||
} catch (IOException e) {
|
||||
logger.error("Updates/Profiles not synced", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (config.netty != null)
|
||||
rebindNettyServerSocket();
|
||||
|
@ -384,47 +383,6 @@ public void run() {
|
|||
}
|
||||
}
|
||||
|
||||
public void syncLauncherBinaries() throws IOException {
|
||||
logger.info("Syncing launcher binaries");
|
||||
|
||||
// Syncing launcher binary
|
||||
logger.info("Syncing launcher binary file");
|
||||
if (!launcherBinary.sync()) logger.warn("Missing launcher binary file");
|
||||
|
||||
// Syncing launcher EXE binary
|
||||
logger.info("Syncing launcher EXE binary file");
|
||||
if (!launcherEXEBinary.sync())
|
||||
logger.warn("Missing launcher EXE binary file");
|
||||
|
||||
}
|
||||
|
||||
public void syncProfilesDir() throws IOException {
|
||||
logger.info("Syncing profiles dir");
|
||||
config.profileProvider.sync();
|
||||
if (config.netty.sendProfileUpdatesEvent) {
|
||||
sendUpdateProfilesEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdateProfilesEvent() {
|
||||
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
|
||||
return;
|
||||
}
|
||||
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
|
||||
Client client = handler.getClient();
|
||||
if (client == null || !client.isAuth) {
|
||||
return;
|
||||
}
|
||||
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
|
||||
event.requestUUID = RequestEvent.eventUUID;
|
||||
handler.service.sendObject(ch, event);
|
||||
});
|
||||
}
|
||||
|
||||
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
||||
updatesManager.syncUpdatesDir(dirs);
|
||||
}
|
||||
|
||||
public void registerObject(String name, Object object) {
|
||||
if (object instanceof Reconfigurable) {
|
||||
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
|
||||
|
@ -454,11 +412,7 @@ public enum LaunchServerEnv {
|
|||
public interface LaunchServerConfigManager {
|
||||
LaunchServerConfig readConfig() throws IOException;
|
||||
|
||||
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
|
||||
|
||||
void writeConfig(LaunchServerConfig config) throws IOException;
|
||||
|
||||
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
|
||||
}
|
||||
|
||||
public static class LaunchServerDirectories {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package pro.gravit.launchserver;
|
||||
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
import pro.gravit.launchserver.manangers.CertificateManager;
|
||||
import pro.gravit.launchserver.manangers.KeyAgreementManager;
|
||||
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
||||
|
@ -11,7 +10,6 @@
|
|||
|
||||
public class LaunchServerBuilder {
|
||||
private LaunchServerConfig config;
|
||||
private LaunchServerRuntimeConfig runtimeConfig;
|
||||
private CommandHandler commandHandler;
|
||||
private LaunchServer.LaunchServerEnv env;
|
||||
private LaunchServerModulesManager modulesManager;
|
||||
|
@ -36,11 +34,6 @@ public LaunchServerBuilder setModulesManager(LaunchServerModulesManager modulesM
|
|||
return this;
|
||||
}
|
||||
|
||||
public LaunchServerBuilder setRuntimeConfig(LaunchServerRuntimeConfig runtimeConfig) {
|
||||
this.runtimeConfig = runtimeConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LaunchServerBuilder setCommandHandler(CommandHandler commandHandler) {
|
||||
this.commandHandler = commandHandler;
|
||||
return this;
|
||||
|
@ -77,7 +70,7 @@ public LaunchServer build() throws Exception {
|
|||
if(shardId == null) {
|
||||
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0"));
|
||||
}
|
||||
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
|
||||
return new LaunchServer(directories, env, config, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
|
||||
}
|
||||
|
||||
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
|
||||
|
@ -95,19 +88,9 @@ public LaunchServerConfig readConfig() {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfig(LaunchServerConfig config) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,12 @@
|
|||
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||
import pro.gravit.launchserver.auth.mix.MixProvider;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.launchserver.components.Component;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
import pro.gravit.launchserver.manangers.CertificateManager;
|
||||
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
||||
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
||||
|
@ -32,7 +31,6 @@
|
|||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
|
@ -49,7 +47,7 @@ public static void main(String[] args) throws Exception {
|
|||
LogHelper.printVersion("LaunchServer");
|
||||
LogHelper.printLicense("LaunchServer");
|
||||
Path dir = IOHelper.WORKING_DIR;
|
||||
Path configFile, runtimeConfigFile;
|
||||
Path configFile;
|
||||
try {
|
||||
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
@ -80,7 +78,6 @@ public static void main(String[] args) throws Exception {
|
|||
}
|
||||
}
|
||||
|
||||
LaunchServerRuntimeConfig runtimeConfig;
|
||||
LaunchServerConfig config;
|
||||
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
|
||||
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
|
||||
|
@ -94,11 +91,6 @@ public static void main(String[] args) throws Exception {
|
|||
} else {
|
||||
configFile = dir.resolve("LaunchServer.json");
|
||||
}
|
||||
if (IOHelper.exists(dir.resolve("RuntimeLaunchServer.conf"))) {
|
||||
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.conf");
|
||||
} else {
|
||||
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.json");
|
||||
}
|
||||
CommandHandler localCommandHandler;
|
||||
try {
|
||||
Class.forName("org.jline.terminal.Terminal");
|
||||
|
@ -116,23 +108,12 @@ public static void main(String[] args) throws Exception {
|
|||
try (BufferedReader reader = IOHelper.newReader(configFile)) {
|
||||
config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
|
||||
}
|
||||
if (!Files.exists(runtimeConfigFile)) {
|
||||
logger.info("Reset LaunchServer runtime config file");
|
||||
runtimeConfig = new LaunchServerRuntimeConfig();
|
||||
runtimeConfig.reset();
|
||||
} else {
|
||||
logger.info("Reading LaunchServer runtime config file");
|
||||
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
||||
runtimeConfig = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
|
||||
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile);
|
||||
LaunchServer server = new LaunchServerBuilder()
|
||||
.setDirectories(directories)
|
||||
.setEnv(env)
|
||||
.setCommandHandler(localCommandHandler)
|
||||
.setRuntimeConfig(runtimeConfig)
|
||||
.setConfig(config)
|
||||
.setModulesManager(modulesManager)
|
||||
.setLaunchServerConfigManager(launchServerConfigManager)
|
||||
|
@ -179,7 +160,7 @@ public static void registerAll() {
|
|||
OptionalAction.registerProviders();
|
||||
OptionalTrigger.registerProviders();
|
||||
MixProvider.registerProviders();
|
||||
ProfileProvider.registerProviders();
|
||||
ProfilesProvider.registerProviders();
|
||||
UpdatesProvider.registerProviders();
|
||||
}
|
||||
|
||||
|
@ -270,11 +251,9 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
|||
|
||||
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
||||
private final Path configFile;
|
||||
private final Path runtimeConfigFile;
|
||||
|
||||
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
|
||||
public BasicLaunchServerConfigManager(Path configFile) {
|
||||
this.configFile = configFile;
|
||||
this.runtimeConfigFile = runtimeConfigFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -286,15 +265,6 @@ public LaunchServerConfig readConfig() throws IOException {
|
|||
return config1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
||||
LaunchServerRuntimeConfig config1;
|
||||
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
||||
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
||||
}
|
||||
return config1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfig(LaunchServerConfig config) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
@ -310,21 +280,5 @@ public void writeConfig(LaunchServerConfig config) throws IOException {
|
|||
IOHelper.write(configFile, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try (Writer writer = IOHelper.newWriter(output)) {
|
||||
if (Launcher.gsonManager.configGson != null) {
|
||||
Launcher.gsonManager.configGson.toJson(config, writer);
|
||||
} else {
|
||||
logger.error("Error writing LaunchServer runtime config file. Gson is null");
|
||||
}
|
||||
}
|
||||
byte[] bytes = output.toByteArray();
|
||||
if(bytes.length > 0) {
|
||||
IOHelper.write(runtimeConfigFile, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ public static void registerProviders() {
|
|||
providers.register("bcrypt", BCryptPasswordVerifier.class);
|
||||
providers.register("accept", AcceptPasswordVerifier.class);
|
||||
providers.register("reject", RejectPasswordVerifier.class);
|
||||
providers.register("django", DjangoPasswordVerifier.class);
|
||||
registeredProviders = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -2,18 +2,11 @@
|
|||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
|
||||
public class StdProtectHandler extends ProtectHandler {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public Map<String, List<String>> profileWhitelist = new HashMap<>();
|
||||
public List<String> allowUpdates = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||
|
@ -22,38 +15,6 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
|||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
|
||||
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGetProfile(ClientProfile profile, Client client) {
|
||||
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeProfile(ClientProfile profile, Client client) {
|
||||
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGetUpdates(String updatesDirName, Client client) {
|
||||
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
|
||||
}
|
||||
|
||||
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
|
||||
if (client.permissions != null) {
|
||||
String permByUUID = property.formatted(profile.getUUID());
|
||||
if (client.permissions.hasPerm(permByUUID)) {
|
||||
return true;
|
||||
}
|
||||
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
|
||||
if (client.permissions.hasPerm(permByTitle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
|
||||
return allowedUsername != null && allowedUsername.contains(client.username);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -2,185 +2,113 @@
|
|||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||
import pro.gravit.launcher.core.serialize.HInput;
|
||||
import pro.gravit.launcher.core.serialize.HOutput;
|
||||
import pro.gravit.launcher.base.config.JsonConfigurable;
|
||||
import pro.gravit.launcher.base.config.SimpleConfigurable;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class LocalUpdatesProvider extends UpdatesProvider {
|
||||
private final transient Logger logger = LogManager.getLogger();
|
||||
public String cacheFile = ".updates-cache";
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public String updatesDir = "updates";
|
||||
public boolean cacheUpdates = true;
|
||||
private volatile transient Map<String, HashedDir> updatesDirMap;
|
||||
|
||||
private void writeCache(Path file) throws IOException {
|
||||
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
|
||||
output.writeLength(updatesDirMap.size(), 0);
|
||||
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
|
||||
output.writeString(entry.getKey(), 0);
|
||||
entry.getValue().write(output);
|
||||
}
|
||||
}
|
||||
logger.debug("Saved {} updates to cache", updatesDirMap.size());
|
||||
}
|
||||
|
||||
private void readCache(Path file) throws IOException {
|
||||
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
|
||||
try (HInput input = new HInput(IOHelper.newInput(file))) {
|
||||
int size = input.readLength(0);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
String name = input.readString(0);
|
||||
HashedDir dir = new HashedDir(input);
|
||||
updatesDirMap.put(name, dir);
|
||||
}
|
||||
}
|
||||
logger.debug("Found {} updates from cache", updatesDirMap.size());
|
||||
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
|
||||
}
|
||||
|
||||
public void readUpdatesFromCache() throws IOException {
|
||||
readCache(Path.of(cacheFile));
|
||||
}
|
||||
|
||||
public void readUpdatesDir() throws IOException {
|
||||
var cacheFilePath = Path.of(cacheFile);
|
||||
if (cacheUpdates) {
|
||||
if (Files.exists(cacheFilePath)) {
|
||||
try {
|
||||
readCache(cacheFilePath);
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
logger.error("Read updates cache failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
sync(null);
|
||||
}
|
||||
public String binaryName = "Launcher";
|
||||
public String buildSecretsFile = "build-secrets.json";
|
||||
public Map<UpdateVariant, String> urls = new HashMap<>(Map.of(
|
||||
UpdateVariant.JAR, "http://localhost:9274/Launcher.jar",
|
||||
UpdateVariant.EXE, "http://localhost:9274/Launcher.exe"
|
||||
));
|
||||
public transient JsonConfigurable<BuildSecretsInfo> buildSecretsJson;
|
||||
private final transient Map<UpdateVariant, byte[]> hashMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
super.init(server);
|
||||
buildSecretsJson = new SimpleConfigurable<>(BuildSecretsInfo.class, Path.of(buildSecretsFile));
|
||||
if(server.env == LaunchServer.LaunchServerEnv.TEST) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!IOHelper.isDir(Path.of(updatesDir)))
|
||||
Files.createDirectory(Path.of(updatesDir));
|
||||
buildSecretsJson.generateConfigIfNotExists();
|
||||
buildSecretsJson.loadConfig();
|
||||
} catch (Exception e) {
|
||||
buildSecretsJson.setConfig(buildSecretsJson.getDefaultConfig());
|
||||
}
|
||||
try {
|
||||
sync(UpdateVariant.JAR);
|
||||
sync(UpdateVariant.EXE);
|
||||
} catch (IOException e) {
|
||||
logger.error("Updates not synced", e);
|
||||
logger.error("Error when syncing binaries", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncInitially() throws IOException {
|
||||
readUpdatesDir();
|
||||
}
|
||||
|
||||
public void sync(Collection<String> dirs) throws IOException {
|
||||
logger.info("Syncing updates dir");
|
||||
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
|
||||
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
|
||||
for (final Path updateDir : dirStream) {
|
||||
if (Files.isHidden(updateDir))
|
||||
continue; // Skip hidden
|
||||
|
||||
// Resolve name and verify is dir
|
||||
String name = IOHelper.getFileName(updateDir);
|
||||
if (!IOHelper.isDir(updateDir)) {
|
||||
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
|
||||
logger.warn("Not update dir: '{}'", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add from previous map (it's guaranteed to be non-null)
|
||||
if (dirs != null && !dirs.contains(name)) {
|
||||
HashedDir hdir = updatesDirMap.get(name);
|
||||
if (hdir != null) {
|
||||
newUpdatesDirMap.put(name, hdir);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync and sign update dir
|
||||
logger.info("Syncing '{}' update dir", name);
|
||||
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
|
||||
newUpdatesDirMap.put(name, updateHDir);
|
||||
}
|
||||
}
|
||||
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
|
||||
if (cacheUpdates) {
|
||||
try {
|
||||
writeCache(Path.of(cacheFile));
|
||||
} catch (Throwable e) {
|
||||
logger.error("Write updates cache failed", e);
|
||||
}
|
||||
}
|
||||
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedDir getUpdatesDir(String updateName) {
|
||||
return updatesDirMap.get(updateName);
|
||||
}
|
||||
|
||||
private Path resolveUpdateName(String updateName) {
|
||||
if(updateName == null) {
|
||||
return Path.of(updatesDir);
|
||||
}
|
||||
return Path.of(updatesDir).resolve(updateName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
|
||||
var path = resolveUpdateName(updateName);
|
||||
for(var e : files.entrySet()) {
|
||||
var target = path.resolve(e.getKey());
|
||||
var source = e.getValue();
|
||||
IOHelper.createParentDirs(target);
|
||||
if(deleteAfterUpload) {
|
||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Path> download(String updateName, List<String> files) {
|
||||
var path = resolveUpdateName(updateName);
|
||||
Map<String, Path> map = new HashMap<>();
|
||||
public void pushUpdate(List<UpdateUploadInfo> files) throws IOException {
|
||||
for(var e : files) {
|
||||
map.put(e, path.resolve(e));
|
||||
IOHelper.copy(e.path(), getUpdate(e.variant()));
|
||||
buildSecretsJson.getConfig().secrets().put(e.variant(), e.secrets());
|
||||
sync(e.variant());
|
||||
}
|
||||
return map;
|
||||
buildSecretsJson.saveConfig();
|
||||
}
|
||||
|
||||
public void sync(UpdateVariant variant) throws IOException {
|
||||
var source = getUpdate(variant);
|
||||
if(!Files.exists(source)) {
|
||||
logger.warn("Dont exist {} binary", variant);
|
||||
return;
|
||||
}
|
||||
byte[] hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, source);
|
||||
hashMap.put(variant, hash);
|
||||
}
|
||||
|
||||
public Path getUpdate(UpdateVariant variant) {
|
||||
String fileName;
|
||||
switch (variant) {
|
||||
case JAR -> {
|
||||
fileName = binaryName.concat(".jar");
|
||||
}
|
||||
case EXE -> {
|
||||
fileName = binaryName.concat(".exe");
|
||||
}
|
||||
default -> {
|
||||
fileName = binaryName;
|
||||
}
|
||||
}
|
||||
return Path.of(updatesDir).resolve(fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String updateName, List<String> files) throws IOException {
|
||||
var path = resolveUpdateName(updateName);
|
||||
for(var e : files) {
|
||||
var target = path.resolve(e);
|
||||
Files.delete(target);
|
||||
public UpdateInfo checkUpdates(UpdateVariant variant, BuildSecretsCheck buildSecretsCheck) {
|
||||
byte[] hash = hashMap.get(variant);
|
||||
if (hash == null) {
|
||||
return null; // We dont have this file
|
||||
}
|
||||
if(checkSecureHash(buildSecretsCheck.secureHash(), buildSecretsCheck.secureSalt(), buildSecretsJson.getConfig().secrets().get(variant).secureToken()) && Arrays.equals(buildSecretsCheck.digest(), hash)) {
|
||||
return null; // Launcher already updated
|
||||
}
|
||||
return new UpdateInfo(urls.get(variant));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String updateName) throws IOException {
|
||||
var path = resolveUpdateName(updateName);
|
||||
IOHelper.deleteDir(path, true);
|
||||
public static final class BuildSecretsInfo {
|
||||
private Map<UpdateVariant, BuildSecrets> secrets = new HashMap<>();
|
||||
|
||||
public BuildSecretsInfo(Map<UpdateVariant, BuildSecrets> secrets) {
|
||||
this.secrets = secrets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(String updateName) throws IOException {
|
||||
var path = resolveUpdateName(updateName);
|
||||
Files.createDirectories(path);
|
||||
public BuildSecretsInfo() {
|
||||
|
||||
}
|
||||
|
||||
public Map<UpdateVariant, BuildSecrets> secrets() {
|
||||
return secrets;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
package pro.gravit.launchserver.auth.updates;
|
||||
|
||||
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class UpdatesProvider {
|
||||
|
||||
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
|
||||
private static boolean registredProviders = false;
|
||||
protected transient LaunchServer server;
|
||||
|
@ -26,27 +29,37 @@ public void init(LaunchServer server) {
|
|||
this.server = server;
|
||||
}
|
||||
|
||||
public void sync() throws IOException {
|
||||
sync(null);
|
||||
public abstract void pushUpdate(List<UpdateUploadInfo> files) throws IOException;
|
||||
public abstract UpdateInfo checkUpdates(UpdateVariant variant, BuildSecretsCheck buildSecretsCheck);
|
||||
|
||||
protected boolean checkSecureHash(String secureHash, String secureSalt, String privateSecureToken) {
|
||||
if (secureHash == null || secureSalt == null) return false;
|
||||
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
||||
privateSecureToken.concat(".").concat(secureSalt));
|
||||
byte[] launcher_hash = Base64.getDecoder().decode(secureHash);
|
||||
return Arrays.equals(normal_hash, launcher_hash);
|
||||
}
|
||||
|
||||
public abstract void syncInitially() throws IOException;
|
||||
|
||||
public abstract void sync(Collection<String> updateNames) throws IOException;
|
||||
|
||||
public abstract HashedDir getUpdatesDir(String updateName);
|
||||
|
||||
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
|
||||
|
||||
public abstract Map<String, Path> download(String updateName, List<String> files);
|
||||
|
||||
public abstract void delete(String updateName, List<String> files) throws IOException;
|
||||
|
||||
public abstract void delete(String updateName) throws IOException;
|
||||
|
||||
public abstract void create(String updateName) throws IOException;
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public enum UpdateVariant {
|
||||
JAR, EXE
|
||||
}
|
||||
|
||||
public record UpdateInfo(String url) {
|
||||
|
||||
}
|
||||
|
||||
public record UpdateUploadInfo(Path path, UpdateVariant variant, BuildSecrets secrets) {
|
||||
|
||||
}
|
||||
|
||||
public record BuildSecrets(String secureToken, byte[] digest) {
|
||||
|
||||
}
|
||||
|
||||
public record BuildSecretsCheck(String secureHash, String secureSalt, byte[] digest) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,8 @@
|
|||
|
||||
public abstract class BinaryPipeline {
|
||||
public final List<LauncherBuildTask> tasks = new ArrayList<>();
|
||||
public final Path buildDir;
|
||||
public final String nameFormat;
|
||||
protected transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
public BinaryPipeline(Path buildDir, String nameFormat) {
|
||||
this.buildDir = buildDir;
|
||||
this.nameFormat = nameFormat;
|
||||
}
|
||||
|
||||
public void addCounted(int count, Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
|
||||
List<LauncherBuildTask> indexes = new ArrayList<>();
|
||||
tasks.stream().filter(pred).forEach(indexes::add);
|
||||
|
@ -77,20 +70,4 @@ public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pr
|
|||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public String nextName(String taskName) {
|
||||
return nameFormat.formatted(taskName);
|
||||
}
|
||||
|
||||
public Path nextPath(String taskName) {
|
||||
return buildDir.resolve(nextName(taskName));
|
||||
}
|
||||
|
||||
public Path nextPath(LauncherBuildTask task) {
|
||||
return nextPath(task.getName());
|
||||
}
|
||||
|
||||
public Path nextLowerPath(LauncherBuildTask task) {
|
||||
return nextPath(CommonHelper.low(task.getName()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launcher.core.serialize.HOutput;
|
||||
import pro.gravit.launcher.core.serialize.stream.StreamObject;
|
||||
import pro.gravit.launchserver.asm.InjectClassAcceptor;
|
||||
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
@ -27,9 +28,7 @@
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
@ -40,16 +39,20 @@
|
|||
import static pro.gravit.utils.helper.IOHelper.newZipEntry;
|
||||
|
||||
public class BuildContext {
|
||||
public final PipelineContext pipelineContext;
|
||||
public final ZipOutputStream output;
|
||||
public final List<JarFile> readerClassPath;
|
||||
public final MainBuildTask task;
|
||||
public final HashSet<String> fileList;
|
||||
public final HashSet<String> clientModules;
|
||||
public final HashSet<String> legacyClientModules;
|
||||
public final Map<String, Object> properties;
|
||||
public final List<MainBuildTask.Transformer> transformers = new ArrayList<>();
|
||||
private Path runtimeDir;
|
||||
private boolean deleteRuntimeDir;
|
||||
|
||||
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
|
||||
public BuildContext(PipelineContext pipelineContext, ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
|
||||
this.pipelineContext = pipelineContext;
|
||||
this.output = output;
|
||||
this.readerClassPath = readerClassPath;
|
||||
this.task = task;
|
||||
|
@ -57,6 +60,9 @@ public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainB
|
|||
fileList = new HashSet<>(1024);
|
||||
clientModules = new HashSet<>();
|
||||
legacyClientModules = new HashSet<>();
|
||||
properties = new HashMap<>();
|
||||
InjectClassAcceptor injectClassAcceptor = new InjectClassAcceptor(properties);
|
||||
transformers.add(injectClassAcceptor);
|
||||
}
|
||||
|
||||
public void pushFile(String filename, InputStream inputStream) throws IOException {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launchserver.binary;
|
||||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -9,14 +10,17 @@
|
|||
public class EXELauncherBinary extends LauncherBinary {
|
||||
|
||||
public EXELauncherBinary(LaunchServer server) {
|
||||
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe");
|
||||
super(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build() throws IOException {
|
||||
if (IOHelper.isFile(syncBinaryFile)) {
|
||||
Files.delete(syncBinaryFile);
|
||||
public UpdatesProvider.UpdateVariant getVariant() {
|
||||
return UpdatesProvider.UpdateVariant.EXE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PipelineContext build() throws IOException {
|
||||
return new PipelineContext(server);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.launchserver.binary.tasks.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -23,7 +24,7 @@ public final class JARLauncherBinary extends LauncherBinary {
|
|||
public final Map<String, Path> files;
|
||||
|
||||
public JARLauncherBinary(LaunchServer server) throws IOException {
|
||||
super(server, resolve(server, ".jar"), "Launcher-%s.jar");
|
||||
super(server);
|
||||
count = new AtomicLong(0);
|
||||
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
|
||||
buildDir = server.dir.resolve("build");
|
||||
|
@ -36,6 +37,11 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdatesProvider.UpdateVariant getVariant() {
|
||||
return UpdatesProvider.UpdateVariant.JAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
tasks.add(new PrepareBuildTask(server));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launchserver.binary;
|
||||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
@ -12,58 +13,40 @@
|
|||
|
||||
public abstract class LauncherBinary extends BinaryPipeline {
|
||||
public final LaunchServer server;
|
||||
public final Path syncBinaryFile;
|
||||
private volatile byte[] digest;
|
||||
public final PipelineContext context;
|
||||
|
||||
protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) {
|
||||
super(server.tmpDir.resolve("build"), nameFormat);
|
||||
protected LauncherBinary(LaunchServer server) {
|
||||
this.server = server;
|
||||
syncBinaryFile = binaryFile;
|
||||
this.context = new PipelineContext(server);
|
||||
}
|
||||
|
||||
public static Path resolve(LaunchServer server, String ext) {
|
||||
return Path.of(server.config.binaryName + ext);
|
||||
}
|
||||
|
||||
public void build() throws IOException {
|
||||
public PipelineContext build() throws IOException {
|
||||
logger.info("Building launcher binary file");
|
||||
Path thisPath = null;
|
||||
long time_start = System.currentTimeMillis();
|
||||
long time_this = time_start;
|
||||
for (LauncherBuildTask task : tasks) {
|
||||
logger.info("Task {}", task.getName());
|
||||
Path oldPath = thisPath;
|
||||
thisPath = task.process(oldPath);
|
||||
Path newPath = task.process(context);
|
||||
if(newPath != null) {
|
||||
context.setLastest(newPath);
|
||||
context.putArtifact(task.getName(), newPath);
|
||||
}
|
||||
long time_task_end = System.currentTimeMillis();
|
||||
long time_task = time_task_end - time_this;
|
||||
time_this = time_task_end;
|
||||
logger.info("Task {} processed from {} millis", task.getName(), time_task);
|
||||
}
|
||||
long time_end = System.currentTimeMillis();
|
||||
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true);
|
||||
IOHelper.deleteDir(buildDir, false);
|
||||
logger.info("Build successful from {} millis", time_end - time_start);
|
||||
return this.context;
|
||||
}
|
||||
|
||||
public final boolean exists() {
|
||||
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
|
||||
}
|
||||
|
||||
public final byte[] getDigest() {
|
||||
return digest;
|
||||
}
|
||||
public abstract UpdatesProvider.UpdateVariant getVariant();
|
||||
|
||||
public void init() {
|
||||
}
|
||||
|
||||
public final boolean sync() {
|
||||
try {
|
||||
var target = syncBinaryFile.toString();
|
||||
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
|
||||
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.asm.ClassMetadataReader;
|
||||
import pro.gravit.launchserver.asm.SafeClassWriter;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -66,8 +67,9 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
Path out = server.launcherBinary.nextPath("post-fixed");
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
Path inputFile = context.getLastest();
|
||||
Path out = context.makeTempPath("post-fixed", ".jar");
|
||||
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
|
||||
apply(inputFile, inputFile, output, server, (e) -> false, true);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launchserver.binary.tasks;
|
||||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -32,8 +33,9 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
Path outputFile = srv.launcherBinary.nextPath("attached");
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
Path inputFile = context.getLastest();
|
||||
Path outputFile = context.makeTempPath("attached", ".jar");
|
||||
try (ZipInputStream input = IOHelper.newZipInput(inputFile);
|
||||
ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputFile))) {
|
||||
ZipEntry e = input.getNextEntry();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.launchserver.helper.SignHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -49,8 +50,8 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
if (signedDataGenerator != null) return inputFile;
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
if (signedDataGenerator != null) return null;
|
||||
try {
|
||||
logger.warn("You are using an auto-generated certificate (sign.enabled false). It is not good");
|
||||
logger.warn("It is highly recommended that you use the correct certificate (sign.enabled true)");
|
||||
|
@ -79,6 +80,6 @@ public Path process(Path inputFile) throws IOException {
|
|||
} catch (OperatorCreationException | CMSException | CertificateException e) {
|
||||
logger.error("Certificate generate failed", e);
|
||||
}
|
||||
return inputFile;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launchserver.binary.tasks;
|
||||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -23,8 +24,9 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
Path output = server.launcherBinary.nextPath(this);
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
Path inputFile = context.getLastest();
|
||||
Path output = context.makeTempPath("compress", ".jar");
|
||||
try (ZipOutputStream outputStream = new ZipOutputStream(IOHelper.newOutput(output))) {
|
||||
outputStream.setMethod(ZipOutputStream.DEFLATED);
|
||||
outputStream.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package pro.gravit.launchserver.binary.tasks;
|
||||
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface LauncherBuildTask {
|
||||
String getName();
|
||||
|
||||
Path process(Path inputFile) throws IOException;
|
||||
Path process(PipelineContext context) throws IOException;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import pro.gravit.launchserver.asm.InjectClassAcceptor;
|
||||
import pro.gravit.launchserver.asm.SafeClassWriter;
|
||||
import pro.gravit.launchserver.binary.BuildContext;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.utils.HookException;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
@ -30,18 +31,14 @@
|
|||
public class MainBuildTask implements LauncherBuildTask {
|
||||
public final ClassMetadataReader reader;
|
||||
public final Set<String> blacklist = new HashSet<>();
|
||||
public final List<Transformer> transformers = new ArrayList<>();
|
||||
public final IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
|
||||
public final IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
|
||||
public final Map<String, Object> properties = new HashMap<>();
|
||||
private final LaunchServer server;
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
public MainBuildTask(LaunchServer srv) {
|
||||
server = srv;
|
||||
reader = new ClassMetadataReader();
|
||||
InjectClassAcceptor injectClassAcceptor = new InjectClassAcceptor(properties);
|
||||
transformers.add(injectClassAcceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,15 +47,16 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputJar) throws IOException {
|
||||
Path outputJar = server.launcherBinary.nextPath(this);
|
||||
public Path process(PipelineContext pipelineContext) throws IOException {
|
||||
Path inputJar = pipelineContext.getLastest();
|
||||
Path outputJar = pipelineContext.makeTempPath("main", ".jar");
|
||||
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
|
||||
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir);
|
||||
initProps();
|
||||
BuildContext context = new BuildContext(pipelineContext, output, reader.getCp(), this, server.launcherBinary.runtimeDir);
|
||||
initProps(context);
|
||||
preBuildHook.hook(context);
|
||||
properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||
postInitProps();
|
||||
context.properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||
context.properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||
postInitProps(context);
|
||||
reader.getCp().add(new JarFile(inputJar.toFile()));
|
||||
for (Path e : server.launcherBinary.coreLibs) {
|
||||
reader.getCp().add(new JarFile(e.toFile()));
|
||||
|
@ -69,7 +67,8 @@ public Path process(Path inputJar) throws IOException {
|
|||
Map<String, byte[]> runtime = new HashMap<>(256);
|
||||
// Write launcher guard dir
|
||||
if (server.config.launcher.encryptRuntime) {
|
||||
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
|
||||
String runtimeEncryptKey = context.pipelineContext.getProperty("runtimeEncryptKey");
|
||||
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtimeEncryptKey, runtime, false);
|
||||
} else {
|
||||
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false);
|
||||
}
|
||||
|
@ -85,7 +84,7 @@ public Path process(Path inputJar) throws IOException {
|
|||
return outputJar;
|
||||
}
|
||||
|
||||
protected void postInitProps() {
|
||||
protected void postInitProps(BuildContext context) {
|
||||
List<byte[]> certificates = Arrays.stream(server.certificateManager.trustManager.getTrusted()).map(e -> {
|
||||
try {
|
||||
return e.getEncoded();
|
||||
|
@ -102,41 +101,41 @@ protected void postInitProps() {
|
|||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
properties.put("launchercore.certificates", certificates);
|
||||
context.properties.put("launchercore.certificates", certificates);
|
||||
}
|
||||
|
||||
protected void initProps() {
|
||||
properties.clear();
|
||||
properties.put("launcher.address", server.config.netty.address);
|
||||
properties.put("launcher.projectName", server.config.projectName);
|
||||
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
||||
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
|
||||
properties.put("launchercore.env", server.config.env);
|
||||
properties.put("launcher.memory", server.config.launcher.memoryLimit);
|
||||
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
|
||||
protected void initProps(BuildContext context) {
|
||||
context.properties.clear();
|
||||
context.properties.put("launcher.address", server.config.netty.address);
|
||||
context.properties.put("launcher.projectName", server.config.projectName);
|
||||
context.properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
||||
context.properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
|
||||
context.properties.put("launchercore.env", server.config.env);
|
||||
context.properties.put("launcher.memory", server.config.launcher.memoryLimit);
|
||||
context.properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
|
||||
if (server.config.launcher.encryptRuntime) {
|
||||
if (server.runtime.runtimeEncryptKey == null)
|
||||
server.runtime.runtimeEncryptKey = SecurityHelper.randomStringToken();
|
||||
properties.put("runtimeconfig.runtimeEncryptKey", server.runtime.runtimeEncryptKey);
|
||||
String runtimeEncryptKey = SecurityHelper.randomStringToken();
|
||||
context.pipelineContext.putProperty("runtimeEncryptKey", runtimeEncryptKey);
|
||||
context.properties.put("runtimeconfig.runtimeEncryptKey", runtimeEncryptKey);
|
||||
}
|
||||
properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
|
||||
properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey);
|
||||
context.properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
|
||||
String checkClientSecret = SecurityHelper.randomStringToken();
|
||||
context.pipelineContext.putProperty("checkClientSecret", checkClientSecret);
|
||||
String launcherSalt = SecurityHelper.randomStringToken();
|
||||
byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
||||
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
|
||||
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
|
||||
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
|
||||
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
|
||||
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
|
||||
server.runtime.buildNumber++;
|
||||
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
|
||||
checkClientSecret.concat(".").concat(launcherSalt));
|
||||
context.properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
|
||||
context.properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
|
||||
String unlockSecret = SecurityHelper.randomStringToken();
|
||||
context.pipelineContext.putProperty("unlockSecret", unlockSecret);
|
||||
context.properties.put("runtimeconfig.unlockSecret", unlockSecret);
|
||||
}
|
||||
|
||||
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
|
||||
byte[] result = bytes;
|
||||
ClassWriter writer;
|
||||
ClassNode cn = null;
|
||||
for (Transformer t : transformers) {
|
||||
for (Transformer t : context.transformers) {
|
||||
if (t instanceof ASMTransformer asmTransformer) {
|
||||
if (cn == null) {
|
||||
ClassReader cr = new ClassReader(result);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.UnpackHelper;
|
||||
|
||||
|
@ -29,7 +30,7 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
server.launcherBinary.coreLibs.clear();
|
||||
server.launcherBinary.addonLibs.clear();
|
||||
server.launcherBinary.files.clear();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.launchserver.binary.SignerJar;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.helper.SignHelper;
|
||||
|
@ -52,8 +53,9 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
Path toRet = srv.launcherBinary.nextPath("signed");
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
Path inputFile = context.getLastest();
|
||||
Path toRet = context.makeTempPath("signed", ".jar");
|
||||
sign(config, inputFile, toRet);
|
||||
return toRet;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ protected boolean showApplyDialog(String text) throws IOException {
|
|||
return response.equals("y");
|
||||
}
|
||||
|
||||
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
||||
public static Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
||||
long total = 0;
|
||||
for (Downloader.SizedFile file : list) {
|
||||
if(file.size < 0) {
|
||||
|
|
|
@ -21,6 +21,5 @@ public String getUsageDescription() {
|
|||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
server.buildLauncherBinaries();
|
||||
server.syncLauncherBinaries();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,10 +33,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
|||
|
||||
// Register sync commands
|
||||
BaseCommandCategory updates = new BaseCommandCategory();
|
||||
updates.registerCommand("indexAsset", new IndexAssetCommand(server));
|
||||
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
|
||||
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
|
||||
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
|
||||
//updates.registerCommand("indexAsset", new IndexAssetCommand(server));
|
||||
//updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
|
||||
updates.registerCommand("sync", new SyncCommand(server));
|
||||
updates.registerCommand("profile", new ProfilesCommand(server));
|
||||
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@
|
|||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class IndexAssetCommand extends Command {
|
||||
public static final String INDEXES_DIR = "indexes";
|
||||
|
@ -50,28 +52,25 @@ public String getUsageDescription() {
|
|||
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 3);
|
||||
/*verifyArgs(args, 3);
|
||||
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
|
||||
String indexFileName = IOHelper.verifyFileName(args[1]);
|
||||
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
|
||||
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
|
||||
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
|
||||
if (outputAssetDir.equals(inputAssetDir))
|
||||
throw new CommandException("Unindexed and indexed asset dirs can't be same");
|
||||
|
||||
// Create new asset dir
|
||||
logger.info("Creating indexed asset dir: '{}'", outputAssetDirName);
|
||||
Files.createDirectory(outputAssetDir);
|
||||
Path inputAssetDir = Path.of(inputAssetDirName);
|
||||
Map<String, Path> uploadMap = new HashMap<>();
|
||||
|
||||
// Index objects
|
||||
JsonObject objects = new JsonObject();
|
||||
logger.info("Indexing objects");
|
||||
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, outputAssetDir), false);
|
||||
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, uploadMap), false);
|
||||
server.config.updatesProvider.upload(outputAssetDirName, uploadMap, false);
|
||||
|
||||
// Write index file
|
||||
logger.info("Writing asset index file: '{}'", indexFileName);
|
||||
|
||||
try (BufferedWriter writer = IOHelper.newWriter(resolveIndexFile(outputAssetDir, indexFileName))) {
|
||||
var indexFile = resolveIndexFile(Path.of(""), indexFileName);
|
||||
|
||||
try (BufferedWriter writer = IOHelper.newWriter(server.config.updatesProvider.upload(outputAssetDirName, indexFile.toString()))) {
|
||||
JsonObject result = new JsonObject();
|
||||
result.add("objects", objects);
|
||||
writer.write(Launcher.gsonManager.gson.toJson(result));
|
||||
|
@ -79,7 +78,7 @@ public void invoke(String... args) throws Exception {
|
|||
|
||||
// Finished
|
||||
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
|
||||
logger.info("Asset successfully indexed: '{}'", inputAssetDirName);
|
||||
logger.info("Asset successfully indexed: '{}'", inputAssetDirName);*/
|
||||
}
|
||||
|
||||
public static class IndexObject {
|
||||
|
@ -95,12 +94,12 @@ public IndexObject(long size, String hash) {
|
|||
private final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
|
||||
private final JsonObject objects;
|
||||
private final Path inputAssetDir;
|
||||
private final Path outputAssetDir;
|
||||
private final Map<String, Path> uploadMap;
|
||||
|
||||
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) {
|
||||
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Map<String, Path> uploadMap) {
|
||||
this.objects = objects;
|
||||
this.inputAssetDir = inputAssetDir;
|
||||
this.outputAssetDir = outputAssetDir;
|
||||
this.uploadMap = uploadMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,7 +111,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
|||
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
|
||||
IndexObject obj = new IndexObject(attrs.size(), digest);
|
||||
objects.add(name, Launcher.gsonManager.gson.toJsonTree(obj));
|
||||
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest));
|
||||
uploadMap.put(resolveObjectFile(Path.of(""), digest).toString(), file);
|
||||
|
||||
// Continue visiting
|
||||
return super.visitFile(file, attrs);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import com.google.gson.JsonParser;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.utils.command.CommandException;
|
||||
|
@ -14,6 +15,7 @@
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class UnindexAssetCommand extends Command {
|
||||
|
@ -35,14 +37,15 @@ public String getUsageDescription() {
|
|||
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 3);
|
||||
/*verifyArgs(args, 3);
|
||||
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
|
||||
String indexFileName = IOHelper.verifyFileName(args[1]);
|
||||
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
|
||||
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
|
||||
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
|
||||
if (outputAssetDir.equals(inputAssetDir))
|
||||
throw new CommandException("Indexed and unindexed asset dirs can't be same");
|
||||
var updatesDir = server.config.updatesProvider.getUpdatesDir(inputAssetDirName);
|
||||
if(updatesDir == null) {
|
||||
server.config.updatesProvider.create(inputAssetDirName);
|
||||
}
|
||||
Path outputAssetDir = Path.of(outputAssetDirName);
|
||||
|
||||
// Create new asset dir
|
||||
logger.info("Creating unindexed asset dir: '{}'", outputAssetDirName);
|
||||
|
@ -51,7 +54,8 @@ public void invoke(String... args) throws Exception {
|
|||
// Read JSON file
|
||||
JsonObject objects;
|
||||
logger.info("Reading asset index file: '{}'", indexFileName);
|
||||
try (BufferedReader reader = IOHelper.newReader(IndexAssetCommand.resolveIndexFile(inputAssetDir, indexFileName))) {
|
||||
Path indexFilePath = IndexAssetCommand.resolveIndexFile(Path.of(""), indexFileName);
|
||||
try (BufferedReader reader = IOHelper.newReader(server.config.updatesProvider.download(inputAssetDirName, indexFilePath.toString()))) {
|
||||
objects = JsonParser.parseReader(reader).getAsJsonObject().get("objects").getAsJsonObject();
|
||||
}
|
||||
|
||||
|
@ -63,12 +67,12 @@ public void invoke(String... args) throws Exception {
|
|||
|
||||
// Copy hashed file to target
|
||||
String hash = member.getValue().getAsJsonObject().get("hash").getAsString();
|
||||
Path source = IndexAssetCommand.resolveObjectFile(inputAssetDir, hash);
|
||||
IOHelper.copy(source, outputAssetDir.resolve(name));
|
||||
Path source = IndexAssetCommand.resolveObjectFile(Path.of(""), hash);
|
||||
server.config.updatesProvider.download(inputAssetDirName, Map.of(source.toString(), outputAssetDir.resolve(name)));
|
||||
}
|
||||
|
||||
// Finished
|
||||
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
|
||||
logger.info("Asset successfully unindexed: '{}'", inputAssetDirName);
|
||||
logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString());*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
|
@ -34,36 +35,14 @@ public String getUsageDescription() {
|
|||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 2);
|
||||
ClientProfile profile;
|
||||
ProfilesProvider.CompletedProfile profile;
|
||||
try {
|
||||
UUID uuid = UUID.fromString(args[0]);
|
||||
profile = server.config.profileProvider.getProfile(uuid);
|
||||
profile = server.config.profilesProvider.get(uuid, null);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
profile = server.config.profileProvider.getProfile(args[0]);
|
||||
profile = server.config.profilesProvider.get(args[0], null);
|
||||
}
|
||||
var builder = new ClientProfileBuilder(profile);
|
||||
builder.setTitle(args[1]);
|
||||
builder.setUuid(UUID.randomUUID());
|
||||
if(profile.getServers().size() == 1) {
|
||||
profile.getServers().getFirst().name = args[1];
|
||||
}
|
||||
logger.info("Copy {} to {}", profile.getDir(), args[1]);
|
||||
var src = server.updatesDir.resolve(profile.getDir());
|
||||
var dest = server.updatesDir.resolve(args[1]);
|
||||
try (Stream<Path> stream = Files.walk(src)) {
|
||||
stream.forEach(source -> {
|
||||
try {
|
||||
IOHelper.copy(source, dest.resolve(src.relativize(source)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.setDir(args[1]);
|
||||
profile = builder.createClientProfile();
|
||||
server.config.profileProvider.addProfile(profile);
|
||||
server.config.profilesProvider.create(args[1], "Description", profile);
|
||||
logger.info("Profile {} cloned from {}", args[1], args[0]);
|
||||
server.syncProfilesDir();
|
||||
server.syncUpdatesDir(List.of(args[1]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -27,24 +28,18 @@ public String getUsageDescription() {
|
|||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 1);
|
||||
ClientProfile profile;
|
||||
ProfilesProvider.CompletedProfile profile;
|
||||
try {
|
||||
UUID uuid = UUID.fromString(args[0]);
|
||||
profile = server.config.profileProvider.getProfile(uuid);
|
||||
profile = server.config.profilesProvider.get(uuid, null);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
profile = server.config.profileProvider.getProfile(args[0]);
|
||||
profile = server.config.profilesProvider.get(args[0], null);
|
||||
}
|
||||
if(profile == null) {
|
||||
logger.error("Profile {} not found", args[0]);
|
||||
return;
|
||||
}
|
||||
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir());
|
||||
if(!showApplyDialog("Continue?")) {
|
||||
return;
|
||||
}
|
||||
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
|
||||
server.config.profileProvider.deleteProfile(profile);
|
||||
logger.info("Delete {}", profile.getDir());
|
||||
server.config.updatesProvider.delete(profile.getDir());
|
||||
logger.info("Delete {}", args[0]);
|
||||
server.config.profilesProvider.delete(profile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ public String getUsageDescription() {
|
|||
|
||||
@Override
|
||||
public void invoke(String... args) {
|
||||
for(var profile : server.getProfiles()) {
|
||||
logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : "");
|
||||
for(var profile : server.config.profilesProvider.getProfiles(null)) {
|
||||
logger.info("{} ({})", profile.getName(), profile.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -6,8 +6,7 @@
|
|||
public class ProfilesCommand extends Command {
|
||||
public ProfilesCommand(LaunchServer server) {
|
||||
super(server);
|
||||
this.childCommands.put("make", new MakeProfileCommand(server));
|
||||
this.childCommands.put("save", new SaveProfilesCommand(server));
|
||||
this.childCommands.put("create", new CreateProfileCommand(server));
|
||||
this.childCommands.put("clone", new CloneProfileCommand(server));
|
||||
this.childCommands.put("list", new ListProfilesCommand(server));
|
||||
this.childCommands.put("delete", new DeleteProfileCommand(server));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -149,8 +149,8 @@ public void invoke(String... args) {
|
|||
case PROD -> printCheckResult("env", "", true);
|
||||
}
|
||||
|
||||
//Profiles
|
||||
for (ClientProfile profile : server.getProfiles()) {
|
||||
//Profiles TODO: Implement
|
||||
/*for (ClientProfile profile : server.config.profilesProvider.getProfiles(null)) {
|
||||
boolean bad = false;
|
||||
String profileModuleName = "profiles.%s".formatted(profile.getTitle());
|
||||
for (String exc : profile.getUpdateExclusions()) {
|
||||
|
@ -177,7 +177,7 @@ public void invoke(String... args) {
|
|||
}
|
||||
if (!bad)
|
||||
printCheckResult(profileModuleName, "", true);
|
||||
}
|
||||
}*/
|
||||
|
||||
//Linux permissions check
|
||||
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {
|
||||
|
@ -206,7 +206,7 @@ public void invoke(String... args) {
|
|||
logger.warn("Write access to LaunchServer.jar. Please use 'chmod 755 LaunchServer.jar'");
|
||||
}
|
||||
if (Files.exists(server.dir.resolve(".keys")) && checkOtherReadOrWriteAccess(server.dir.resolve(".keys"))) {
|
||||
logger.warn("Write or read access to .keys directory. Please use 'chmod -R 600 .keys'");
|
||||
logger.warn("Write or read access to .keys directory. Please use 'chmod -r 700.keys");
|
||||
}
|
||||
if (Files.exists(server.dir.resolve("LaunchServerConfig.json")) && checkOtherReadOrWriteAccess(server.dir.resolve("LaunchServerConfig.json"))) {
|
||||
logger.warn("Write or read access to LaunchServerConfig.json. Please use 'chmod 600 LaunchServerConfig.json'");
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.utils.command.SubCommand;
|
||||
|
||||
|
@ -28,9 +29,9 @@ public void invoke(String... args) throws Exception {
|
|||
public void invoke(String... args) {
|
||||
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
|
||||
boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]);
|
||||
ClientProfile profile = null;
|
||||
for (ClientProfile p : server.getProfiles()) {
|
||||
if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) {
|
||||
ProfilesProvider.UncompletedProfile profile = null;
|
||||
for (var p : server.config.profilesProvider.getProfiles(null)) {
|
||||
if (p.getName().equals(args[0]) || p.getUuid().toString().equals(args[0])) {
|
||||
profile = p;
|
||||
break;
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ public void invoke(String... args) {
|
|||
logger.error("AuthId {} not found", args[1]);
|
||||
return;
|
||||
}
|
||||
String token = server.authManager.newCheckServerToken(profile != null ? profile.getUUID().toString() : args[0], pair.name, publicOnly);
|
||||
String token = server.authManager.newCheckServerToken(profile != null ? profile.getUuid().toString() : args[0], pair.name, publicOnly);
|
||||
logger.info("Server token {} authId {}: {}", args[0], pair.name, token);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -6,12 +6,7 @@
|
|||
public class SyncCommand extends Command {
|
||||
public SyncCommand(LaunchServer server) {
|
||||
super(server);
|
||||
this.childCommands.put("profiles", new SyncProfilesCommand(server));
|
||||
this.childCommands.put("binaries", new SyncBinariesCommand(server));
|
||||
this.childCommands.put("updates", new SyncUpdatesCommand(server));
|
||||
this.childCommands.put("up", new SyncUPCommand(server));
|
||||
this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server));
|
||||
this.childCommands.put("updatescache", new SyncUpdatesCacheCommand(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.Reconfigurable;
|
||||
import pro.gravit.launchserver.binary.PipelineContext;
|
||||
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
||||
import pro.gravit.utils.command.Command;
|
||||
import pro.gravit.utils.command.SubCommand;
|
||||
|
@ -137,19 +138,18 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
if (!component.enabled) {
|
||||
return inputFile;
|
||||
return null;
|
||||
}
|
||||
LauncherBuildTask task = server.launcherBinary.getTaskBefore((x) -> proguardTaskName.equals(x.getName())).get();
|
||||
Path lastPath = server.launcherBinary.nextPath(task);
|
||||
Path lastPath = context.getLastest();
|
||||
if(Files.notExists(lastPath)) {
|
||||
logger.error("{} not exist. Multi-Release JAR fix not applied!", lastPath);
|
||||
return inputFile;
|
||||
return null;
|
||||
}
|
||||
Path outputPath = server.launcherBinary.nextPath(this);
|
||||
Path outputPath = context.makeTempPath("multirelease-fix", "jar");
|
||||
try(ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outputPath.toFile()))) {
|
||||
try(ZipInputStream input = new ZipInputStream(new FileInputStream(inputFile.toFile()))) {
|
||||
try(ZipInputStream input = new ZipInputStream(new FileInputStream(lastPath.toFile()))) {
|
||||
ZipEntry entry = input.getNextEntry();
|
||||
while(entry != null) {
|
||||
ZipEntry newEntry = new ZipEntry(entry.getName());
|
||||
|
@ -193,8 +193,8 @@ public String getName() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path process(Path inputFile) throws IOException {
|
||||
Path outputJar = server.launcherBinary.nextLowerPath(this);
|
||||
public Path process(PipelineContext context) throws IOException {
|
||||
Path outputJar = context.makeTempPath("proguard", "jar");
|
||||
if (component.enabled) {
|
||||
if (!checkJMods(IOHelper.JVM_DIR.resolve("jmods"))) {
|
||||
throw new RuntimeException("Java path: %s is not JDK! Please install JDK".formatted(IOHelper.JVM_DIR));
|
||||
|
@ -221,7 +221,7 @@ public Path process(Path inputFile) throws IOException {
|
|||
);
|
||||
}
|
||||
args.add("proguard.ProGuard");
|
||||
proguardConf.buildConfig(args, inputFile, outputJar, jfxPath == null ? new Path[0] : new Path[]{jfxPath});
|
||||
proguardConf.buildConfig(args, context.getLastest(), outputJar, jfxPath == null ? new Path[0] : new Path[]{jfxPath});
|
||||
|
||||
Process process = new ProcessBuilder()
|
||||
.command(args)
|
||||
|
@ -240,8 +240,9 @@ public Path process(Path inputFile) throws IOException {
|
|||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
}
|
||||
} else
|
||||
IOHelper.copy(inputFile, outputJar);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return outputJar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.LocalProfileProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.LocalProfilesProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||
import pro.gravit.launchserver.auth.protect.StdProtectHandler;
|
||||
import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
|
||||
|
@ -28,7 +28,7 @@
|
|||
public final class LaunchServerConfig {
|
||||
private final static List<String> oldMirrorList = List.of("https://mirror.gravit.pro/5.2.x/", "https://mirror.gravit.pro/5.3.x/",
|
||||
"https://mirror.gravitlauncher.com/5.2.x/", "https://mirror.gravitlauncher.com/5.3.x/", "https://mirror.gravitlauncher.com/5.4.x/",
|
||||
"https://mirror.gravitlauncher.com/5.5.x/");
|
||||
"https://mirror.gravitlauncher.com/5.5.x/", "https://mirror.gravitlauncher.com/5.6.x/");
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public String projectName;
|
||||
public String[] mirrors;
|
||||
|
@ -39,7 +39,7 @@ public final class LaunchServerConfig {
|
|||
// Handlers & Providers
|
||||
public ProtectHandler protectHandler;
|
||||
public Map<String, Component> components;
|
||||
public ProfileProvider profileProvider = new LocalProfileProvider();
|
||||
public ProfilesProvider profilesProvider = new LocalProfilesProvider();
|
||||
public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
|
||||
public NettyConfig netty;
|
||||
public LauncherConf launcher;
|
||||
|
@ -49,7 +49,7 @@ public final class LaunchServerConfig {
|
|||
|
||||
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
||||
LaunchServerConfig newConfig = new LaunchServerConfig();
|
||||
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.6.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
|
||||
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.7.x/"};
|
||||
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
|
||||
newConfig.auth = new HashMap<>();
|
||||
AuthProviderPair a = new AuthProviderPair(new RejectAuthCoreProvider(),
|
||||
|
@ -85,7 +85,8 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
|||
newConfig.components.put("authLimiter", authLimiterComponent);
|
||||
ProGuardComponent proGuardComponent = new ProGuardComponent();
|
||||
newConfig.components.put("proguard", proGuardComponent);
|
||||
newConfig.profileProvider = new LocalProfileProvider();
|
||||
newConfig.profilesProvider = new LocalProfilesProvider();
|
||||
newConfig.updatesProvider = new LocalUpdatesProvider();
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
|
@ -151,7 +152,7 @@ public void verify() {
|
|||
for (int i = 0; i < mirrors.length; ++i) {
|
||||
if (mirrors[i] != null && oldMirrorList.contains(mirrors[i])) {
|
||||
logger.warn("Replace mirror '{}' to 'https://mirror.gravitlauncher.com/5.6.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'", mirrors[i]);
|
||||
mirrors[i] = "https://mirror.gravitlauncher.com/5.6.x/";
|
||||
mirrors[i] = "https://mirror.gravitlauncher.com/5.7.x/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,9 +168,9 @@ public void init(LaunchServer.ReloadType type) {
|
|||
server.registerObject("protectHandler", protectHandler);
|
||||
protectHandler.init(server);
|
||||
}
|
||||
if(profileProvider != null) {
|
||||
server.registerObject("profileProvider", profileProvider);
|
||||
profileProvider.init(server);
|
||||
if(profilesProvider != null) {
|
||||
server.registerObject("profileProvider", profilesProvider);
|
||||
profilesProvider.init(server);
|
||||
}
|
||||
if(updatesProvider != null) {
|
||||
server.registerObject("updatesProvider", updatesProvider);
|
||||
|
@ -215,9 +216,9 @@ public void close(LaunchServer.ReloadType type) {
|
|||
server.unregisterObject("protectHandler", protectHandler);
|
||||
protectHandler.close();
|
||||
}
|
||||
if(profileProvider != null) {
|
||||
server.unregisterObject("profileProvider", profileProvider);
|
||||
profileProvider.close();
|
||||
if(profilesProvider != null) {
|
||||
server.unregisterObject("profilesProvider", profilesProvider);
|
||||
profilesProvider.close();
|
||||
}
|
||||
if(updatesProvider != null) {
|
||||
server.unregisterObject("updatesProvider", updatesProvider);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -152,12 +152,8 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
|
|||
}
|
||||
}
|
||||
}
|
||||
builder.setMinJavaVersion(17);
|
||||
builder.setRecommendJavaVersion(17);
|
||||
if(version.compareTo(ClientProfileVersions.MINECRAFT_1_20_3) >= 0) {
|
||||
builder.setMinJavaVersion(21);
|
||||
builder.setRecommendJavaVersion(21);
|
||||
}
|
||||
jvmArgs.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
jvmArgs.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
|
||||
builder.setJvmArgs(jvmArgs);
|
||||
|
|
|
@ -47,7 +47,7 @@ public void init() {
|
|||
MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get();
|
||||
mainTask.preBuildHook.registerHook((buildContext) -> {
|
||||
for (ModuleEntity e : launcherModules) {
|
||||
if (e.propertyMap != null) buildContext.task.properties.putAll(e.propertyMap);
|
||||
if (e.propertyMap != null) buildContext.properties.putAll(e.propertyMap);
|
||||
if(e.modernModule) {
|
||||
buildContext.clientModules.add(e.moduleMainClass);
|
||||
} else {
|
||||
|
|
|
@ -191,7 +191,7 @@ public PlayerProfile getPlayerProfile(Client client) {
|
|||
playerProfile = getPlayerProfile(client.auth, user);
|
||||
if (playerProfile != null) return playerProfile;
|
||||
if (client.auth.textureProvider != null) {
|
||||
return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getTitle(), client.auth.textureProvider, new HashMap<>());
|
||||
return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getName(), client.auth.textureProvider, new HashMap<>());
|
||||
}
|
||||
// Return combined profile
|
||||
return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>());
|
||||
|
@ -282,14 +282,6 @@ public AuthRequest.AuthPasswordInterface decryptPassword(AuthRequest.AuthPasswor
|
|||
}
|
||||
|
||||
private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.AuthPasswordInterface password) throws AuthException {
|
||||
if (password instanceof AuthAESPassword authAESPassword) {
|
||||
try {
|
||||
return new AuthPlainPassword(IOHelper.decode(SecurityHelper.decrypt(server.runtime.passwordEncryptKey
|
||||
, authAESPassword.password)));
|
||||
} catch (Exception ignored) {
|
||||
throw new AuthException("Password decryption error");
|
||||
}
|
||||
}
|
||||
if (password instanceof AuthRSAPassword authRSAPassword) {
|
||||
try {
|
||||
Cipher cipher = SecurityHelper.newRSADecryptCipher(server.keyAgreementManager.rsaPrivateKey);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||
import pro.gravit.launchserver.auth.mix.MixProvider;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
|
@ -48,7 +48,7 @@ public void registerAdapters(GsonBuilder builder) {
|
|||
builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers));
|
||||
builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers));
|
||||
builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers));
|
||||
builder.registerTypeAdapter(ProfileProvider.class, new UniversalJsonAdapter<>(ProfileProvider.providers));
|
||||
builder.registerTypeAdapter(ProfilesProvider.class, new UniversalJsonAdapter<>(ProfilesProvider.providers));
|
||||
builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
|
||||
modulesManager.invokeEvent(new PreGsonPhase(builder));
|
||||
//ClientWebSocketService.appendTypeAdapters(builder);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import pro.gravit.launcher.base.modules.LauncherInitContext;
|
||||
import pro.gravit.launcher.base.modules.LauncherModule;
|
||||
import pro.gravit.launcher.base.modules.LauncherModuleInfo;
|
||||
import pro.gravit.launcher.base.modules.LauncherModuleInfoBuilder;
|
||||
import pro.gravit.launcher.base.modules.events.InitPhase;
|
||||
import pro.gravit.utils.Version;
|
||||
|
||||
public class LaunchServerCoreModule extends LauncherModule {
|
||||
public LaunchServerCoreModule() {
|
||||
super(new LauncherModuleInfo("LaunchServerCore", Version.getVersion()));
|
||||
super(new LauncherModuleInfoBuilder().setName("LaunchServerCore").setVersion(Version.getVersion()).createLauncherModuleInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -19,7 +20,7 @@ public class Client {
|
|||
public String auth_id;
|
||||
public long timestamp;
|
||||
public AuthResponse.ConnectTypes type;
|
||||
public ClientProfile profile;
|
||||
public ProfilesProvider.CompletedProfile profile;
|
||||
public boolean isAuth;
|
||||
public boolean checkSign;
|
||||
public ClientPermissions permissions;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.base.events.request.CurrentUserRequestEvent;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.core.UserSession;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
|
||||
|
@ -12,6 +13,13 @@ public static CurrentUserRequestEvent.UserInfo collectUserInfoFromClient(LaunchS
|
|||
CurrentUserRequestEvent.UserInfo result = new CurrentUserRequestEvent.UserInfo();
|
||||
if (client.auth != null && client.isAuth && client.username != null) {
|
||||
result.playerProfile = server.authManager.getPlayerProfile(client);
|
||||
if(server.config.protectHandler.allowGetAccessToken(new AuthResponse.AuthContext(client, client.username,
|
||||
null, null, client.type, client.auth))) {
|
||||
UserSession session = client.sessionObject;
|
||||
if(session != null) {
|
||||
result.accessToken = session.getMinecraftAccessToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
result.permissions = client.permissions;
|
||||
return result;
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
|
||||
|
@ -13,21 +14,22 @@
|
|||
import java.util.Set;
|
||||
|
||||
public class ProfilesResponse extends SimpleResponse {
|
||||
@Deprecated
|
||||
|
||||
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
|
||||
List<ClientProfile> profileList;
|
||||
Set<ClientProfile> serverProfiles = server.getProfiles();
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
|
||||
profileList = new ArrayList<>(4);
|
||||
for (ClientProfile profile : serverProfiles) {
|
||||
if (protectHandler.canGetProfile(profile, client)) {
|
||||
profileList.add(profile);
|
||||
}
|
||||
}
|
||||
Set<ProfilesProvider.UncompletedProfile> serverProfiles = server.config.profilesProvider.getProfiles(client);
|
||||
List<ClientProfile> profiles = new ArrayList<>();
|
||||
for(var uncompleted : serverProfiles) {
|
||||
if(uncompleted instanceof ProfilesProvider.CompletedProfile completed) {
|
||||
profiles.add(completed.getProfile());
|
||||
} else {
|
||||
profileList = List.copyOf(serverProfiles);
|
||||
profiles.add(new ClientProfileBuilder()
|
||||
.setUuid(uncompleted.getUuid())
|
||||
.setTitle(uncompleted.getName())
|
||||
.setInfo(uncompleted.getDescription())
|
||||
.createClientProfile());
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,10 +39,6 @@ public String getType() {
|
|||
|
||||
@Override
|
||||
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetProfiles(client)) {
|
||||
sendError("Access denied");
|
||||
return;
|
||||
}
|
||||
sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client)));
|
||||
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.base.events.request.SetProfileRequestEvent;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
import pro.gravit.utils.HookException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SetProfileResponse extends SimpleResponse {
|
||||
public String client;
|
||||
public UUID uuid;
|
||||
public String tag;
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
|
@ -25,20 +24,13 @@ public void execute(ChannelHandlerContext ctx, Client client) {
|
|||
} catch (HookException e) {
|
||||
sendError(e.getMessage());
|
||||
}
|
||||
Collection<ClientProfile> profiles = server.getProfiles();
|
||||
for (ClientProfile p : profiles) {
|
||||
if (p.getTitle().equals(this.client)) {
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler &&
|
||||
!profilesProtectHandler.canChangeProfile(p, client)) {
|
||||
sendError("Access denied");
|
||||
return;
|
||||
}
|
||||
client.profile = p;
|
||||
sendResult(new SetProfileRequestEvent(p));
|
||||
return;
|
||||
}
|
||||
}
|
||||
var profile = server.config.profilesProvider.get(uuid, tag);
|
||||
if(profile == null) {
|
||||
sendError("Profile not found");
|
||||
return;
|
||||
}
|
||||
client.profile = profile;
|
||||
sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import pro.gravit.launcher.base.events.request.LauncherRequestEvent;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
|
@ -43,28 +44,18 @@ public void execute(ChannelHandlerContext ctx, Client client) {
|
|||
bytes = Base64.getDecoder().decode(hash);
|
||||
else
|
||||
bytes = digest;
|
||||
if (launcher_type == 1) // JAR
|
||||
{
|
||||
byte[] hash = server.launcherBinary.getDigest();
|
||||
if (hash == null)
|
||||
service.sendObjectAndClose(ctx, new LauncherRequestEvent(true, server.config.netty.launcherURL));
|
||||
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) {
|
||||
client.checkSign = true;
|
||||
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
|
||||
} else {
|
||||
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, null, 0));
|
||||
UpdatesProvider.UpdateVariant variant = UpdatesProvider.UpdateVariant.JAR;
|
||||
if(launcher_type == 2) {
|
||||
variant = UpdatesProvider.UpdateVariant.EXE;
|
||||
}
|
||||
} else if (launcher_type == 2) //EXE
|
||||
{
|
||||
byte[] hash = server.launcherEXEBinary.getDigest();
|
||||
if (hash == null) sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL));
|
||||
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) {
|
||||
client.checkSign = true;
|
||||
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
|
||||
byte[] hashToCheck = bytes;
|
||||
UpdatesProvider.UpdateInfo info = server.config.updatesProvider.checkUpdates(variant, new UpdatesProvider.BuildSecretsCheck(secureHash, secureSalt, hashToCheck));
|
||||
if (info != null) {
|
||||
sendResult(new LauncherRequestEvent(true, info.url()));
|
||||
} else {
|
||||
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0));
|
||||
client.checkSign = true;
|
||||
sendResult(new LauncherRequestEvent(false, null, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
|
||||
}
|
||||
} else sendError("Request launcher type error");
|
||||
}
|
||||
|
||||
public String createLauncherExtendedToken() {
|
||||
|
@ -76,14 +67,6 @@ public String createLauncherExtendedToken() {
|
|||
.compact();
|
||||
}
|
||||
|
||||
private boolean checkSecure(String hash, String salt) {
|
||||
if (hash == null || salt == null) return false;
|
||||
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
||||
server.runtime.clientCheckSecret.concat(".").concat(salt));
|
||||
byte[] launcher_hash = Base64.getDecoder().decode(hash);
|
||||
return Arrays.equals(normal_hash, launcher_hash);
|
||||
}
|
||||
|
||||
public static class LauncherTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
|
||||
private final JwtParser parser;
|
||||
private final Logger logger = LogManager.getLogger();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.base.events.request.UpdateRequestEvent;
|
||||
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
|
@ -19,15 +18,22 @@ public String getType() {
|
|||
|
||||
@Override
|
||||
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetUpdates(dirName, client)) {
|
||||
sendError("Access denied");
|
||||
return;
|
||||
}
|
||||
if (dirName == null) {
|
||||
sendError("Invalid request");
|
||||
return;
|
||||
}
|
||||
HashedDir dir = server.updatesManager.getUpdate(dirName);
|
||||
if(client.profile == null) {
|
||||
sendError("Profile not setted");
|
||||
return;
|
||||
}
|
||||
HashedDir dir = null;
|
||||
if(dirName.equals(client.profile.getProfile().getDir())) {
|
||||
dir = client.profile.getClientDir();
|
||||
} else if(dirName.equals(client.profile.getProfile().getAssetDir())) {
|
||||
dir = client.profile.getAssetDir();
|
||||
} else {
|
||||
dir = server.config.profilesProvider.getUnconnectedDirectory(dirName);
|
||||
}
|
||||
if (dir == null) {
|
||||
sendError("Directory %s not found".formatted(dirName));
|
||||
return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"features": [],
|
||||
"features": ["new-api"],
|
||||
"info": []
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
import org.junit.jupiter.api.io.TempDir;
|
||||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
import pro.gravit.launchserver.impl.TestLaunchServerConfigManager;
|
||||
import pro.gravit.launchserver.manangers.CertificateManager;
|
||||
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
||||
|
@ -33,13 +32,11 @@ public static void prepare() throws Throwable {
|
|||
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
||||
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
|
||||
Launcher.gsonManager.initGson();
|
||||
LaunchServerRuntimeConfig runtimeConfig = new LaunchServerRuntimeConfig();
|
||||
LaunchServerBuilder builder = new LaunchServerBuilder();
|
||||
launchServerConfigManager = new TestLaunchServerConfigManager();
|
||||
builder.setDir(dir)
|
||||
.setEnv(LaunchServer.LaunchServerEnv.TEST)
|
||||
.setConfig(config)
|
||||
.setRuntimeConfig(runtimeConfig)
|
||||
.setCertificateManager(new CertificateManager())
|
||||
.setLaunchServerConfigManager(launchServerConfigManager)
|
||||
.setModulesManager(modulesManager)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import org.junit.jupiter.api.io.TempDir;
|
||||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
import pro.gravit.launchserver.impl.TestLaunchServerConfigManager;
|
||||
import pro.gravit.launchserver.manangers.CertificateManager;
|
||||
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
||||
|
@ -33,12 +32,10 @@ public static void prepare() throws Throwable {
|
|||
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
||||
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
|
||||
Launcher.gsonManager.initGson();
|
||||
LaunchServerRuntimeConfig runtimeConfig = new LaunchServerRuntimeConfig();
|
||||
LaunchServerBuilder builder = new LaunchServerBuilder();
|
||||
builder.setDir(dir)
|
||||
.setEnv(LaunchServer.LaunchServerEnv.TEST)
|
||||
.setConfig(config)
|
||||
.setRuntimeConfig(runtimeConfig)
|
||||
.setCertificateManager(new CertificateManager())
|
||||
.setLaunchServerConfigManager(new TestLaunchServerConfigManager())
|
||||
.setModulesManager(modulesManager)
|
||||
|
|
|
@ -2,16 +2,12 @@
|
|||
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||
|
||||
public class TestLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
||||
public LaunchServerConfig config;
|
||||
public LaunchServerRuntimeConfig runtimeConfig;
|
||||
|
||||
public TestLaunchServerConfigManager() {
|
||||
config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
||||
runtimeConfig = new LaunchServerRuntimeConfig();
|
||||
runtimeConfig.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,18 +15,8 @@ public LaunchServerConfig readConfig() {
|
|||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
||||
return runtimeConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfig(LaunchServerConfig config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'com.gradleup.shadow'
|
||||
|
||||
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
|||
url "https://repo.spring.io/plugins-release/"
|
||||
}
|
||||
}
|
||||
sourceCompatibility = '17'
|
||||
targetCompatibility = '17'
|
||||
sourceCompatibility = '21'
|
||||
targetCompatibility = '21'
|
||||
|
||||
configurations {
|
||||
bundle
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
|
||||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launcher.base.LauncherConfig;
|
||||
import pro.gravit.launcher.base.request.*;
|
||||
import pro.gravit.launcher.client.*;
|
||||
import pro.gravit.launcher.core.api.LauncherAPI;
|
||||
import pro.gravit.launcher.core.api.LauncherAPIHolder;
|
||||
import pro.gravit.launcher.core.api.features.*;
|
||||
import pro.gravit.launcher.core.backend.LauncherBackendAPIHolder;
|
||||
import pro.gravit.launcher.runtime.backend.LauncherBackendImpl;
|
||||
import pro.gravit.launcher.runtime.client.*;
|
||||
import pro.gravit.launcher.runtime.client.events.ClientEngineInitPhase;
|
||||
import pro.gravit.launcher.client.events.ClientExitPhase;
|
||||
import pro.gravit.launcher.runtime.client.events.ClientPreGuiPhase;
|
||||
import pro.gravit.launcher.runtime.console.GetPublicKeyCommand;
|
||||
import pro.gravit.launcher.runtime.console.ModulesCommand;
|
||||
import pro.gravit.launcher.runtime.console.SignDataCommand;
|
||||
import pro.gravit.launcher.runtime.gui.NoRuntimeProvider;
|
||||
import pro.gravit.launcher.runtime.gui.RuntimeProvider;
|
||||
import pro.gravit.launcher.runtime.managers.ConsoleManager;
|
||||
|
@ -19,25 +23,16 @@
|
|||
import pro.gravit.launcher.base.modules.events.PreConfigPhase;
|
||||
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
|
||||
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
|
||||
import pro.gravit.launcher.base.request.Request;
|
||||
import pro.gravit.launcher.base.request.RequestException;
|
||||
import pro.gravit.launcher.base.request.RequestService;
|
||||
import pro.gravit.launcher.base.request.auth.*;
|
||||
import pro.gravit.launcher.base.request.websockets.OfflineRequestService;
|
||||
import pro.gravit.launcher.base.request.websockets.StdWebSocketService;
|
||||
import pro.gravit.launcher.start.RuntimeModuleManager;
|
||||
import pro.gravit.utils.helper.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
@ -48,8 +43,6 @@ public class LauncherEngine {
|
|||
// Instance
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
public RuntimeProvider runtimeProvider;
|
||||
public ECPublicKey publicKey;
|
||||
public ECPrivateKey privateKey;
|
||||
public Class<? extends RuntimeProvider> basicRuntimeProvider;
|
||||
|
||||
private LauncherEngine(boolean clientInstance, Class<? extends RuntimeProvider> basicRuntimeProvider) {
|
||||
|
@ -176,37 +169,9 @@ public static LauncherEngine newInstance(boolean clientInstance, Class<? extends
|
|||
return new LauncherEngine(clientInstance, basicRuntimeProvider);
|
||||
}
|
||||
|
||||
public ECPublicKey getClientPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] sign(byte[] bytes) {
|
||||
return SecurityHelper.sign(bytes, privateKey);
|
||||
}
|
||||
|
||||
public void readKeys() throws IOException, InvalidKeySpecException {
|
||||
if (privateKey != null || publicKey != null) return;
|
||||
Path dir = DirBridge.dir;
|
||||
Path publicKeyFile = dir.resolve("public.key");
|
||||
Path privateKeyFile = dir.resolve("private.key");
|
||||
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
|
||||
LogHelper.info("Reading EC keypair");
|
||||
publicKey = SecurityHelper.toPublicECDSAKey(IOHelper.read(publicKeyFile));
|
||||
privateKey = SecurityHelper.toPrivateECDSAKey(IOHelper.read(privateKeyFile));
|
||||
} else {
|
||||
LogHelper.info("Generating EC keypair");
|
||||
KeyPair pair = SecurityHelper.genECDSAKeyPair(new SecureRandom());
|
||||
publicKey = (ECPublicKey) pair.getPublic();
|
||||
privateKey = (ECPrivateKey) pair.getPrivate();
|
||||
|
||||
// Write key pair list
|
||||
LogHelper.info("Writing EC keypair list");
|
||||
IOHelper.write(publicKeyFile, publicKey.getEncoded());
|
||||
IOHelper.write(privateKeyFile, privateKey.getEncoded());
|
||||
}
|
||||
}
|
||||
|
||||
public void start(String... args) throws Throwable {
|
||||
var config = Launcher.getConfig();
|
||||
config.apply();
|
||||
//Launcher.modulesManager = new ClientModuleManager(this);
|
||||
ClientPreGuiPhase event = new ClientPreGuiPhase(null);
|
||||
LauncherEngine.modulesManager.invokeEvent(event);
|
||||
|
@ -215,7 +180,7 @@ public void start(String... args) throws Throwable {
|
|||
runtimeProvider.init(clientInstance);
|
||||
//runtimeProvider.preLoad();
|
||||
if (!Request.isAvailable()) {
|
||||
String address = Launcher.getConfig().address;
|
||||
String address = config.address;
|
||||
LogHelper.debug("Start async connection to %s", address);
|
||||
RequestService service;
|
||||
try {
|
||||
|
@ -243,10 +208,22 @@ public void start(String... args) throws Throwable {
|
|||
}
|
||||
Request.startAutoRefresh();
|
||||
Request.getRequestService().registerEventHandler(new BasicLauncherEventHandler());
|
||||
// Init New API
|
||||
LauncherAPIHolder.setCoreAPI(new RequestCoreFeatureAPIImpl(Request.getRequestService()));
|
||||
LauncherAPIHolder.setCreateApiFactory((authId) -> {
|
||||
var impl = new RequestFeatureAPIImpl(Request.getRequestService(), authId);
|
||||
return new LauncherAPI(Map.of(
|
||||
AuthFeatureAPI.class, impl,
|
||||
UserFeatureAPI.class, impl,
|
||||
ProfileFeatureAPI.class, impl,
|
||||
TextureUploadFeatureAPI.class, impl,
|
||||
HardwareVerificationFeatureAPI.class, impl));
|
||||
});
|
||||
LauncherBackendAPIHolder.setApi(new LauncherBackendImpl());
|
||||
//
|
||||
Objects.requireNonNull(args, "args");
|
||||
if (started.getAndSet(true))
|
||||
throw new IllegalStateException("Launcher has been already started");
|
||||
readKeys();
|
||||
registerCommands();
|
||||
LauncherEngine.modulesManager.invokeEvent(new ClientEngineInitPhase(this));
|
||||
runtimeProvider.preLoad();
|
||||
|
@ -255,8 +232,6 @@ public void start(String... args) throws Throwable {
|
|||
}
|
||||
|
||||
private void registerCommands() {
|
||||
ConsoleManager.handler.registerCommand("getpublickey", new GetPublicKeyCommand(this));
|
||||
ConsoleManager.handler.registerCommand("signdata", new SignDataCommand(this));
|
||||
ConsoleManager.handler.registerCommand("modules", new ModulesCommand());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package pro.gravit.launcher.runtime;
|
||||
|
||||
import pro.gravit.launcher.runtime.client.UserSettings;
|
||||
import pro.gravit.launcher.core.backend.UserSettings;
|
||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import pro.gravit.launcher.base.Launcher;
|
||||
import pro.gravit.launcher.base.LauncherConfig;
|
||||
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
|
||||
import pro.gravit.launcher.client.ClientLauncherEntryPoint;
|
||||
import pro.gravit.launcher.client.ClientParams;
|
||||
import pro.gravit.launcher.runtime.LauncherEngine;
|
||||
|
@ -69,6 +70,12 @@ public ClientLauncherProcess(Path clientDir, Path assetDir,
|
|||
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
|
||||
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
|
||||
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir) {
|
||||
this(clientDir, assetDir, javaVersion, resourcePackDir, profile, playerProfile, view, accessToken, clientHDir, assetHDir, jvmHDir, null, null);
|
||||
}
|
||||
|
||||
public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersion javaVersion, Path resourcePackDir,
|
||||
ClientProfile profile, PlayerProfile playerProfile, OptionalView view, String accessToken,
|
||||
HashedDir clientHDir, HashedDir assetHDir, HashedDir jvmHDir, AuthRequestEvent.OAuthRequestEvent oAuthRequestEvent, String authId) {
|
||||
this.javaVersion = javaVersion;
|
||||
this.workDir = clientDir.toAbsolutePath();
|
||||
this.executeFile = IOHelper.resolveJavaBin(this.javaVersion.jvmDir);
|
||||
|
@ -77,6 +84,8 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi
|
|||
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
|
||||
this.params.assetDir = assetDir.toAbsolutePath().toString();
|
||||
this.params.timestamp = System.currentTimeMillis();
|
||||
this.params.oauth = oAuthRequestEvent;
|
||||
this.params.authId = authId;
|
||||
Path nativesPath;
|
||||
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
|
||||
nativesPath = workDir.resolve("natives");
|
||||
|
@ -119,10 +128,8 @@ private void applyClientProfile() {
|
|||
if (params.ram > 0) {
|
||||
this.jvmArgs.add("-Xmx" + params.ram + 'M');
|
||||
}
|
||||
this.params.oauth = Request.getOAuth();
|
||||
if (this.params.oauth == null) {
|
||||
throw new UnsupportedOperationException("Legacy session not supported");
|
||||
} else {
|
||||
this.params.oauth = Request.getOAuth();
|
||||
this.params.authId = Request.getAuthId();
|
||||
this.params.oauthExpiredTime = Request.getTokenExpiredTime();
|
||||
this.params.extendedTokens = Request.getExtendedTokens();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launcher.runtime.client;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import pro.gravit.launcher.core.backend.UserSettings;
|
||||
import pro.gravit.launcher.start.RuntimeModuleManager;
|
||||
import pro.gravit.launcher.core.managers.GsonManager;
|
||||
import pro.gravit.launcher.base.modules.events.PreGsonPhase;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import com.google.gson.JsonParser;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
|
||||
import pro.gravit.launcher.core.backend.LauncherBackendAPI;
|
||||
import pro.gravit.launcher.core.serialize.HInput;
|
||||
import pro.gravit.launcher.core.serialize.HOutput;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
@ -18,6 +19,7 @@
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -208,7 +210,7 @@ public Result ping() throws IOException {
|
|||
}
|
||||
}
|
||||
|
||||
public static final class Result {
|
||||
public static final class Result implements LauncherBackendAPI.ServerPingInfo {
|
||||
|
||||
public final int onlinePlayers;
|
||||
|
||||
|
@ -228,5 +230,20 @@ public Result(int onlinePlayers, int maxPlayers, String raw) {
|
|||
public boolean isOverfilled() {
|
||||
return onlinePlayers >= maxPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxOnline() {
|
||||
return maxPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOnline() {
|
||||
return onlinePlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPlayerNames() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ public static void initialize() throws Exception {
|
|||
config.unlockSecret = DebugProperties.UNLOCK_SECRET;
|
||||
Launcher.setConfig(config);
|
||||
Launcher.applyLauncherEnv(DebugProperties.ENV);
|
||||
config.apply();
|
||||
LauncherEngine.modulesManager = new RuntimeModuleManager();
|
||||
LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule());
|
||||
for (String moduleClassName : DebugProperties.MODULE_CLASSES) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
import oshi.hardware.*;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -104,6 +106,27 @@ public String getBaseboardSerialNumber() {
|
|||
return hardware.getComputerSystem().getBaseboard().getSerialNumber();
|
||||
}
|
||||
|
||||
public HardwareVerificationFeatureAPI.HardwareStatisticData getStatisticData() {
|
||||
return new HardwareVerificationFeatureAPI.HardwareStatisticData(
|
||||
JVMHelper.ARCH.toHardwareFeatureArch(JVMHelper.ARCH_TYPE),
|
||||
JVMHelper.OS.toHardwareFeatureOs(JVMHelper.OS_TYPE),
|
||||
getTotalMemory(),
|
||||
getProcessorLogicalCount(),
|
||||
getProcessorPhysicalCount(),
|
||||
getProcessorMaxFreq(),
|
||||
isBattery(),
|
||||
getGraphicCardName()
|
||||
);
|
||||
}
|
||||
|
||||
public HardwareVerificationFeatureAPI.HardwareIdentifyData getIdentifyData() {
|
||||
return new HardwareVerificationFeatureAPI.HardwareIdentifyData(
|
||||
getBaseboardSerialNumber(),
|
||||
getHWDiskID(),
|
||||
getDisplayID()
|
||||
);
|
||||
}
|
||||
|
||||
public HardwareReportRequest.HardwareInfo getHardwareInfo(boolean needSerial) {
|
||||
HardwareReportRequest.HardwareInfo info = new HardwareReportRequest.HardwareInfo();
|
||||
info.bitness = getBitness();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
sourceCompatibility = '17'
|
||||
targetCompatibility = '17'
|
||||
sourceCompatibility = '21'
|
||||
targetCompatibility = '21'
|
||||
|
||||
dependencies {
|
||||
api project(':LauncherCore')
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package pro.gravit.launcher.base;
|
||||
|
||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||
import pro.gravit.launcher.core.api.model.UserPermissions;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ClientPermissions {
|
||||
public class ClientPermissions implements UserPermissions {
|
||||
public static final ClientPermissions DEFAULT = new ClientPermissions();
|
||||
@LauncherNetworkAPI
|
||||
private List<String> roles;
|
||||
|
@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() {
|
|||
return perm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(String role) {
|
||||
return roles != null && roles.contains(role);
|
||||
}
|
||||
|
@ -46,6 +48,7 @@ public synchronized void compile() {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPerm(String action) {
|
||||
if (available == null) {
|
||||
compile();
|
||||
|
|
|
@ -234,7 +234,7 @@ protected DownloadTask sendAsync(SizedFile file, URI baseUri, Path targetDir, Do
|
|||
return task.get();
|
||||
}
|
||||
|
||||
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
||||
public static URI makeURI(URI baseUri, String filePath) throws URISyntaxException {
|
||||
URI uri;
|
||||
if(baseUri != null) {
|
||||
String scheme = baseUri.getScheme();
|
||||
|
@ -247,6 +247,11 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy
|
|||
} else {
|
||||
uri = new URI(filePath);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISyntaxException {
|
||||
var uri = makeURI(baseUri, filePath);
|
||||
return HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(uri)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.launcher.base;
|
||||
|
||||
import pro.gravit.launcher.core.BuildInParams;
|
||||
import pro.gravit.launcher.core.LauncherInject;
|
||||
import pro.gravit.launcher.core.LauncherInjectionConstructor;
|
||||
import pro.gravit.launcher.core.LauncherTrustManager;
|
||||
|
@ -8,6 +9,7 @@
|
|||
import pro.gravit.launcher.core.serialize.HInput;
|
||||
import pro.gravit.launcher.core.serialize.HOutput;
|
||||
import pro.gravit.launcher.core.serialize.stream.StreamObject;
|
||||
import pro.gravit.utils.Version;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
@ -122,6 +124,12 @@ public LauncherConfig(String address, Map<String, byte[]> runtime, String projec
|
|||
runtimeEncryptKey = null;
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
Version version = Version.getVersion();
|
||||
BuildInParams.setVersion(new Version(version.major, version.minor, version.patch, (int) buildNumber));
|
||||
BuildInParams.setProjectName(projectName);
|
||||
}
|
||||
|
||||
public static void initModules(LauncherModulesManager modulesManager) {
|
||||
if(JVMHelper.JVM_VERSION >= 17) {
|
||||
modulesClasses.addAll(ModernModulesClass.modulesClasses);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package pro.gravit.launcher.base.events.request;
|
||||
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.core.api.features.TextureUploadFeatureAPI;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class AssetUploadInfoRequestEvent extends RequestEvent {
|
||||
public class AssetUploadInfoRequestEvent extends RequestEvent implements TextureUploadFeatureAPI.TextureUploadInfo {
|
||||
public Set<String> available;
|
||||
public SlimSupportConf slimSupportConf;
|
||||
|
||||
|
@ -18,6 +19,16 @@ public String getType() {
|
|||
return "assetUploadInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequireManualSlimSkinSelect() {
|
||||
return slimSupportConf == SlimSupportConf.USER;
|
||||
}
|
||||
|
||||
public enum SlimSupportConf {
|
||||
UNSUPPORTED, USER, SERVER
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package pro.gravit.launcher.base.events.request;
|
||||
|
||||
import pro.gravit.launcher.base.ClientPermissions;
|
||||
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
|
||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.base.profiles.PlayerProfile;
|
||||
|
@ -67,7 +68,15 @@ public String getType() {
|
|||
return "auth";
|
||||
}
|
||||
|
||||
public static class OAuthRequestEvent {
|
||||
public CurrentUserRequestEvent.UserInfo makeUserInfo() {
|
||||
var userInfo = new CurrentUserRequestEvent.UserInfo();
|
||||
userInfo.accessToken = accessToken;
|
||||
userInfo.permissions = permissions;
|
||||
userInfo.playerProfile = playerProfile;
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public static class OAuthRequestEvent implements AuthFeatureAPI.AuthToken {
|
||||
public final String accessToken;
|
||||
public final String refreshToken;
|
||||
public final long expire;
|
||||
|
@ -77,5 +86,26 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
|
|||
this.refreshToken = refreshToken;
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
public OAuthRequestEvent(AuthFeatureAPI.AuthToken token) {
|
||||
this.accessToken = token.getAccessToken();
|
||||
this.refreshToken = token.getRefreshToken();
|
||||
this.expire = token.getExpire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpire() {
|
||||
return expire;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
import pro.gravit.launcher.base.ClientPermissions;
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.base.profiles.PlayerProfile;
|
||||
import pro.gravit.launcher.core.api.model.SelfUser;
|
||||
import pro.gravit.launcher.core.api.model.Texture;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CurrentUserRequestEvent extends RequestEvent {
|
||||
public final UserInfo userInfo;
|
||||
|
@ -16,7 +21,7 @@ public String getType() {
|
|||
return "currentUser";
|
||||
}
|
||||
|
||||
public static class UserInfo {
|
||||
public static class UserInfo implements SelfUser {
|
||||
public ClientPermissions permissions;
|
||||
public String accessToken;
|
||||
public PlayerProfile playerProfile;
|
||||
|
@ -29,5 +34,35 @@ public UserInfo(ClientPermissions permissions, String accessToken, PlayerProfile
|
|||
this.accessToken = accessToken;
|
||||
this.playerProfile = playerProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPermissions getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return playerProfile.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return playerProfile.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Texture> getAssets() {
|
||||
return playerProfile.getAssets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return playerProfile.getProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
package pro.gravit.launcher.base.events.request;
|
||||
|
||||
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails;
|
||||
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
|
||||
import pro.gravit.launcher.core.LauncherNetworkAPI;
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.core.api.method.AuthMethod;
|
||||
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
|
||||
import pro.gravit.launcher.core.api.method.details.AuthPasswordDetails;
|
||||
import pro.gravit.launcher.core.api.method.details.AuthWebDetails;
|
||||
import pro.gravit.utils.TypeSerializeInterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -37,10 +44,11 @@ public enum ServerFeature {
|
|||
}
|
||||
}
|
||||
|
||||
public interface AuthAvailabilityDetails extends TypeSerializeInterface {
|
||||
public interface AuthAvailabilityDetails extends AuthMethodDetails, TypeSerializeInterface {
|
||||
AuthMethodDetails toAuthMethodDetails();
|
||||
}
|
||||
|
||||
public static class AuthAvailability {
|
||||
public static class AuthAvailability implements AuthMethod {
|
||||
public final List<AuthAvailabilityDetails> details;
|
||||
@LauncherNetworkAPI
|
||||
public String name;
|
||||
|
@ -59,5 +67,34 @@ public AuthAvailability(List<AuthAvailabilityDetails> details, String name, Stri
|
|||
this.visible = visible;
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthMethodDetails> getDetails() {
|
||||
List<AuthMethodDetails> convert = new ArrayList<>();
|
||||
for(var e : details) {
|
||||
convert.add(e.toAuthMethodDetails());
|
||||
}
|
||||
return convert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getFeatures() {
|
||||
return features;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package pro.gravit.launcher.base.events.request;
|
||||
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
|
||||
|
||||
public class GetSecureLevelInfoRequestEvent extends RequestEvent {
|
||||
public class GetSecureLevelInfoRequestEvent extends RequestEvent implements HardwareVerificationFeatureAPI.SecurityLevelInfo {
|
||||
public final byte[] verifySecureKey;
|
||||
public boolean enabled;
|
||||
|
||||
|
@ -19,4 +20,14 @@ public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey, boolean enabled) {
|
|||
public String getType() {
|
||||
return "getSecureLevelInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignData() {
|
||||
return verifySecureKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,17 @@ public class SetProfileRequestEvent extends RequestEvent {
|
|||
private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541");
|
||||
@LauncherNetworkAPI
|
||||
public final ClientProfile newProfile;
|
||||
@LauncherNetworkAPI
|
||||
public final String tag;
|
||||
|
||||
public SetProfileRequestEvent(ClientProfile newProfile) {
|
||||
this.newProfile = newProfile;
|
||||
this.tag = null;
|
||||
}
|
||||
|
||||
public SetProfileRequestEvent(ClientProfile newProfile, String tag) {
|
||||
this.newProfile = newProfile;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import pro.gravit.launcher.base.events.ExtendedTokenRequestEvent;
|
||||
import pro.gravit.launcher.base.events.RequestEvent;
|
||||
import pro.gravit.launcher.core.api.features.HardwareVerificationFeatureAPI;
|
||||
|
||||
public class VerifySecureLevelKeyRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent {
|
||||
public class VerifySecureLevelKeyRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent, HardwareVerificationFeatureAPI.SecurityLevelVerification {
|
||||
public boolean needHardwareInfo;
|
||||
public boolean onlyStatisticInfo;
|
||||
public String extendedToken;
|
||||
|
@ -42,4 +43,17 @@ public String getExtendedToken() {
|
|||
public long getExtendedTokenExpire() {
|
||||
return expire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HardwareCollectLevel getHardwareCollectLevel() {
|
||||
if(needHardwareInfo) {
|
||||
if(onlyStatisticInfo) {
|
||||
return HardwareCollectLevel.ONLY_STATISTIC;
|
||||
} else {
|
||||
return HardwareCollectLevel.ALL;
|
||||
}
|
||||
} else {
|
||||
return HardwareCollectLevel.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue