Compare commits

..

No commits in common. "master" and "vv5.6.1" have entirely different histories.

105 changed files with 784 additions and 2628 deletions

View file

@ -6,28 +6,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
submodules: recursive
- name: Cache Gradle
uses: actions/cache@v4
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Set up JDK 21
uses: actions/setup-java@v4
uses: actions/setup-java@v1
with:
java-version: 21
distribution: temurin
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5
@ -44,7 +42,7 @@ jobs:
cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: Launcher
path: artifacts
@ -65,7 +63,7 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
if: startsWith(github.event.ref, 'refs/tags')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -13,24 +13,20 @@
maven {
url "https://jitpack.io/"
}
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
}
sourceCompatibility = '21'
targetCompatibility = '21'
configurations {
compileOnlyA
bundleOnly
bundle
hikari
pack
launch4j
bundleOnly.extendsFrom bundle
api.extendsFrom bundle, pack
api.extendsFrom bundle, hikari, pack, launch4j
}
jar {
@ -65,6 +61,7 @@
dependsOn jar
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Automatic-Module-Name": "launchserver"
)
from sourceSets.main.output
@ -73,60 +70,68 @@
dependencies {
pack project(':LauncherAPI')
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1'
bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix']
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix']
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: rootProject['verBcpkix']
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64'
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
// Netty
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
//
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn']
bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn']
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
bundle group: 'com.h2database', name: 'h2', version: rootProject['verH2Conn']
bundle group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j']
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: rootProject['verLog4j']
bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt']
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
annotationProcessor(group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'])
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
bundle 'io.micrometer:micrometer-core:1.14.4'
bundle('com.zaxxer:HikariCP:6.2.1') {
hikari 'io.micrometer:micrometer-core:1.8.4'
hikari('com.zaxxer:HikariCP:5.0.1') {
exclude group: 'javassist'
exclude group: 'io.micrometer'
exclude group: 'org.slf4j'
}
compileOnlyA group: 'com.google.guava', name: 'guava', version: rootProject['verGuavaC']
// Do not update (laggy deps).
compileOnlyA 'log4j:log4j:1.2.17'
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.14.1'
}
tasks.register('hikari', Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/hikaricp"
from configurations.hikari
}
tasks.register('dumpLibs', Copy) {
duplicatesStrategy = 'EXCLUDE'
dependsOn tasks.hikari
into "$buildDir/libs/libraries"
from configurations.bundleOnly
}
tasks.register('dumpCompileOnlyLibs', Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/launcher-libraries-compile"
from configurations.compileOnlyA
}
tasks.register('bundle', Zip) {
duplicatesStrategy = 'EXCLUDE'
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
archiveFileName = 'LaunchServer.zip'
destinationDirectory = file("$buildDir")
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
from(tasks.jar)
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
}
@ -137,7 +142,7 @@ pack project(':LauncherAPI')
from parent.childProjects.Launcher.tasks.dumpLibs
}
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
publishing {

View file

@ -2,6 +2,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.modules.events.ClosePhase;
@ -20,18 +21,21 @@
import pro.gravit.launchserver.modules.events.*;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.SocketCommandServer;
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
import pro.gravit.launchserver.socket.response.auth.ProfilesResponse;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.utils.command.Command;
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;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.KeyStore;
import java.time.Duration;
import java.time.Instant;
@ -66,7 +70,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/**
* The path to the folder with updates/webroot
*/
@Deprecated
public final Path updatesDir;
// Constant paths
@ -77,11 +80,8 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/**
* The path to the folder with profiles
*/
public final Path profilesDir;
public final Path tmpDir;
public final Path modulesDir;
public final Path launcherModulesDir;
public final Path librariesDir;
public final Path controlFile;
/**
* This object contains runtime configuration
*/
@ -110,13 +110,14 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
// Server
public final CommandHandler commandHandler;
public final NettyServerSocketHandler nettyServerSocketHandler;
public final SocketCommandServer socketCommandServer;
public final ScheduledExecutorService service;
public final AtomicBoolean started = new AtomicBoolean(false);
public final LauncherModuleLoader launcherModuleLoader;
private final Logger logger = LogManager.getLogger();
public final int shardId;
public LaunchServerConfig config;
// Updates and profiles
private volatile Set<ClientProfile> profilesList;
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
this.dir = directories.dir;
@ -125,6 +126,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.config = config;
this.launchServerConfigManager = launchServerConfigManager;
this.modulesManager = modulesManager;
this.profilesDir = directories.profilesDir;
this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler;
@ -134,10 +136,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
launcherLibraries = directories.launcherLibrariesDir;
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
launcherPack = directories.launcherPackDir;
modulesDir = directories.modules;
launcherModulesDir = directories.launcherModules;
librariesDir = directories.librariesDir;
controlFile = directories.controlFile;
this.shardId = shardId;
if(!Files.isDirectory(launcherPack)) {
Files.createDirectories(launcherPack);
@ -189,7 +187,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
}
launcherModuleLoader.init();
nettyServerSocketHandler = new NettyServerSocketHandler(this);
socketCommandServer = new SocketCommandServer(commandHandler, controlFile);
if(config.sign.checkCertificateExpired) {
checkCertificateExpired();
service.scheduleAtFixedRate(this::checkCertificateExpired, 24, 24, TimeUnit.HOURS);
@ -323,14 +320,12 @@ public void close() throws Exception {
logger.info("LaunchServer stopped");
}
@Deprecated
public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles();
return profilesList;
}
@Deprecated
public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException();
this.profilesList = Collections.unmodifiableSet(profilesList);
}
public void rebindNettyServerSocket() {
@ -353,17 +348,17 @@ 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 {
if (!IOHelper.isDir(updatesDir))
Files.createDirectory(updatesDir);
updatesManager.readUpdatesDir();
// Sync profiles dir
if (!IOHelper.isDir(profilesDir))
Files.createDirectory(profilesDir);
syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) {
logger.error("Updates/Profiles not synced", e);
@ -398,7 +393,12 @@ public void syncLauncherBinaries() throws IOException {
public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir");
config.profileProvider.sync();
List<ClientProfile> newProfies = new LinkedList<>();
IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false);
// Sort and set new profiles
newProfies.sort(Comparator.comparing(a -> a));
profilesList = Set.copyOf(newProfies);
if (config.netty.sendProfileUpdatesEvent) {
sendUpdateProfilesEvent();
}
@ -413,7 +413,7 @@ private void sendUpdateProfilesEvent() {
if (client == null || !client.isAuth) {
return;
}
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
ProfilesRequestEvent event = new ProfilesRequestEvent(ProfilesResponse.getListVisibleProfiles(this, client));
event.requestUUID = RequestEvent.eventUUID;
handler.service.sendObject(ch, event);
});
@ -459,12 +459,38 @@ public interface LaunchServerConfigManager {
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Collection<ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Collection<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();
profile.setProfileFilePath(file);
// Add SIGNED profile to result list
result.add(profile);
return super.visitFile(file, attrs);
}
}
public static class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates",
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys", MODULES = "modules", LAUNCHER_MODULES = "launcher-modules", LIBRARIES = "libraries", CONTROL_FILE = "control-file";
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys";
public Path updatesDir;
public Path librariesDir;
public Path profilesDir;
public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
@ -472,23 +498,17 @@ public static class LaunchServerDirectories {
public Path dir;
public Path trustStore;
public Path tmpDir;
public Path modules;
public Path launcherModules;
public Path controlFile;
public void collect() {
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
if (profilesDir == null) profilesDir = getPath(PROFILES_NAME);
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if (launcherPackDir == null)
if(launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_NAME);
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
if (modules == null) modules = getPath(MODULES);
if (launcherModules == null) launcherModules = getPath(LAUNCHER_MODULES);
if (librariesDir == null) librariesDir = getPath(LIBRARIES);
if (controlFile == null) controlFile = getPath(CONTROL_FILE);
if (tmpDir == null)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("launchserver-%s".formatted(SecurityHelper.randomStringToken()));
}

View file

@ -13,10 +13,8 @@
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.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;
@ -53,16 +51,13 @@ public static void main(String[] args) throws Exception {
try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider());
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
} catch (ClassNotFoundException ex) {
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
return;
}
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
directories.collect();
CertificateManager certificateManager = new CertificateManager();
try {
certificateManager.readTrustStore(directories.trustStore);
certificateManager.readTrustStore(dir.resolve("truststore"));
} catch (CertificateException e) {
throw new IOException(e);
}
@ -83,7 +78,7 @@ 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);
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload();
modulesManager.initModules(null);
registerAll();
@ -128,6 +123,8 @@ public static void main(String[] args) throws Exception {
}
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)
.setEnv(env)
@ -138,24 +135,7 @@ public static void main(String[] args) throws Exception {
.setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager)
.build();
List<String> allArgs = List.of(args);
boolean isPrepareMode = prepareMode || allArgs.contains("--prepare");
boolean isRunCommand = false;
String runCommand = null;
for(var e : allArgs) {
if(e.equals("--run")) {
isRunCommand = true;
continue;
}
if(isRunCommand) {
runCommand = e;
isRunCommand = false;
}
}
if(runCommand != null) {
localCommandHandler.eval(runCommand, false);
}
if (!isPrepareMode) {
if (!prepareMode) {
server.run();
} else {
server.close();
@ -179,8 +159,6 @@ public static void registerAll() {
OptionalAction.registerProviders();
OptionalTrigger.registerProviders();
MixProvider.registerProviders();
ProfileProvider.registerProviders();
UpdatesProvider.registerProviders();
}
private static void printExperimentalBranch() {
@ -223,7 +201,7 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
address = System.getProperty("launchserver.address", null);
}
if (address == null) {
System.out.println("External launchServer address:port (default: localhost:9274): ");
System.out.println("LaunchServer address(default: localhost): ");
address = commandHandler.readLine();
}
String projectName = System.getenv("PROJECTNAME");
@ -237,29 +215,18 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
newConfig.setProjectName(projectName);
}
if (address == null || address.isEmpty()) {
logger.error("Address null. Using localhost:9274");
address = "localhost:9274";
logger.error("Address null. Using localhost");
address = "localhost";
}
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
logger.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft";
}
int port = 9274;
if(address.contains(":")) {
String portString = address.substring(address.indexOf(':')+1);
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
logger.warn("Unknown port {}, using 9274", portString);
}
} else {
logger.info("Address {} doesn't contains port (you want to use nginx?)", address);
}
newConfig.netty.address = "ws://" + address + "/api";
newConfig.netty.downloadURL = "http://" + address + "/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + "/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + "/Launcher.exe";
newConfig.netty.binds[0].port = port;
newConfig.netty.address = "ws://" + address + ":9274/api";
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
// Write LaunchServer config
logger.info("Writing LaunchServer config file");

View file

@ -18,10 +18,8 @@
import java.util.stream.Stream;
public class Main {
private static final List<String> classpathOnly = List.of("proguard", "jline", "progressbar", "kotlin");
private static final List<String> classpathOnly = List.of("proguard", "jline", "kotlin", "epoll");
private static final String LOG4J_PROPERTY = "log4j2.configurationFile";
private static final String DEBUG_PROPERTY = "launchserver.main.debug";
private static final String LIBRARIES_PROPERTY = "launchserver.dir.libraries";
private static boolean isClasspathOnly(Path path) {
var fileName = path.getFileName().toString();
for(var e : classpathOnly) {
@ -57,9 +55,8 @@ public static void main(String[] args) throws Throwable {
ModuleLaunch launch = new ModuleLaunch();
LaunchOptions options = new LaunchOptions();
options.moduleConf = new LaunchOptions.ModuleConf();
Path librariesPath = Path.of(System.getProperty(LIBRARIES_PROPERTY, "libraries"));
List<Path> libraries;
try(Stream<Path> files = Files.walk(librariesPath, FileVisitOption.FOLLOW_LINKS)) {
try(Stream<Path> files = Files.walk(Path.of("libraries"), FileVisitOption.FOLLOW_LINKS)) {
libraries = new ArrayList<>(files.filter(e -> e.getFileName().toString().endsWith(".jar")).toList());
}
List<Path> classpath = new ArrayList<>();
@ -81,14 +78,6 @@ public static void main(String[] args) throws Throwable {
ModuleLayer.Controller controller = (ModuleLayer.Controller) control.getJava9ModuleController();
LaunchServerControlHolder.setControl(control);
LaunchServerControlHolder.setController(controller);
if(Boolean.getBoolean(DEBUG_PROPERTY)) {
for(var e : controller.layer().modules()) {
System.out.printf("Module %s\n", e.getName());
for(var p : e.getPackages()) {
System.out.printf("Package %s\n", p);
}
}
}
launch.launch("pro.gravit.launchserver.LaunchServerStarter", null, Arrays.asList(args));
}
}

View file

@ -16,10 +16,6 @@ public AuthException(String message) {
super(message);
}
public AuthException(String message, Throwable cause) {
super(message, cause);
}
public static AuthException need2FA() {
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
}

View file

@ -1,62 +0,0 @@
package pro.gravit.launchserver.auth;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.function.Consumer;
public class HikariSQLSourceConfig implements SQLSourceConfig {
private transient volatile HikariDataSource dataSource;
private String dsClass;
private Properties dsProps;
private String driverClass;
private String jdbcUrl;
private String username;
private String password;
private boolean initializeAtStart;
public void init() {
if(initializeAtStart) {
initializeConnection();
}
}
private void initializeConnection() {
if (dataSource != null) {
return;
}
HikariConfig config = new HikariConfig();
consumeIfNotNull(config::setDataSourceClassName, dsClass);
consumeIfNotNull(config::setDataSourceProperties, dsProps);
consumeIfNotNull(config::setDriverClassName, driverClass);
consumeIfNotNull(config::setJdbcUrl, jdbcUrl);
consumeIfNotNull(config::setUsername, username);
consumeIfNotNull(config::setPassword, password);
this.dataSource = new HikariDataSource(config);
}
@Override
public Connection getConnection() throws SQLException {
if(dataSource == null && !initializeAtStart) {
synchronized (this) {
initializeConnection();
}
}
return dataSource.getConnection();
}
@Override
public void close() {
dataSource.close();
}
private static <T> void consumeIfNotNull(Consumer<T> consumer, T val) {
if (val != null) {
consumer.accept(val);
}
}
}

View file

@ -174,7 +174,7 @@ public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException
}
@Override
public User checkServer(Client client, String username, String serverID) {
public User checkServer(Client client, String username, String serverID) throws IOException {
SQLUser user = (SQLUser) getUserByUsername(username);
if (user == null) {
return null;
@ -276,7 +276,7 @@ public void close() {
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn)) : null;
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn))) : null;
}
public ClientPermissions requestPermissions (String uuid) throws SQLException
@ -286,19 +286,14 @@ public ClientPermissions requestPermissions (String uuid) throws SQLException
}
private SQLUser queryUser(String sql, String value) throws SQLException {
SQLUser user;
try (Connection c = getSQLConfig().getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
user = constructUser(set);
return constructUser(set);
}
}
if(user != null) {
user.permissions = requestPermissions(user.uuid.toString());
}
return user;
}
private List<String> queryPermissions(String sql, String value) throws SQLException {
@ -345,14 +340,15 @@ public static class SQLUser implements User {
protected String accessToken;
protected String serverId;
protected final String password;
protected ClientPermissions permissions;
protected final ClientPermissions permissions;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password) {
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
}
@Override

View file

@ -21,7 +21,6 @@
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.auth.core.openid.OpenIDAuthCoreProvider;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -54,8 +53,6 @@ public static void registerProviders() {
providers.register("postgresql", PostgresSQLCoreProvider.class);
providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("merge", MergeAuthCoreProvider.class);
providers.register("openid", OpenIDAuthCoreProvider.class);
providers.register("sql", SQLCoreProvider.class);
registredProviders = true;
}
}

View file

@ -1,5 +1,6 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
@ -43,7 +44,6 @@ public SQLSourceConfig getSQLConfig() {
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)
@ -72,7 +72,7 @@ protected String makeUserCols() {
@Override
protected MySQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn)), set.getLong(hardwareIdColumn)) : null;
}
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
@ -336,8 +336,8 @@ public String toString() {
public static class MySQLUser extends SQLUser {
protected long hwidId;
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
super(uuid, username, accessToken, serverId, password);
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
super(uuid, username, accessToken, serverId, password, permissions);
this.hwidId = hwidId;
}

View file

@ -1,7 +1,5 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
@ -12,10 +10,4 @@ public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
public SQLSourceConfig getSQLConfig() {
return postgresSQLHolder;
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'postgresql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
}
}

View file

@ -41,12 +41,12 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
}
@Override
public User checkServer(Client client, String username, String serverID) {
public User checkServer(Client client, String username, String serverID) throws IOException {
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException {
return false;
}

View file

@ -1,391 +0,0 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportExtendedCheckServer;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
import pro.gravit.launchserver.socket.Client;
import java.io.IOException;
import java.sql.*;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class SQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware, AuthSupportExtendedCheckServer {
public HikariSQLSourceConfig holder;
@Override
public void close() {
super.close();
holder.close();
}
@Override
public SQLSourceConfig getSQLConfig() {
return holder;
}
public String hardwareIdColumn;
public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog";
public double criticalCompareLevel = 1.0;
private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById;
private transient String sqlCreateHardware;
private transient String sqlCreateHWIDLog;
private transient String sqlUpdateHardwarePublicKey;
private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId;
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
holder.init();
super.init(server, pair);
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE publicKey = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlFindHardwareById == null)
sqlFindHardwareById = "SELECT %s FROM %s WHERE id = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlUsersByHwidId == null)
sqlUsersByHwidId = "SELECT %s FROM %s WHERE %s = ?".formatted(userInfoCols, table, hardwareIdColumn);
if (sqlFindHardwareByData == null)
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
if (sqlCreateHardware == null)
sqlCreateHardware = "INSERT INTO %s (publickey, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, graphicCard, battery, banned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
if (sqlCreateHWIDLog == null)
sqlCreateHWIDLog = "INSERT INTO %s (hwidId, newPublicKey) VALUES (?, ?)".formatted(tableHWIDLog);
if (sqlUpdateHardwarePublicKey == null)
sqlUpdateHardwarePublicKey = "UPDATE %s SET publicKey = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateHardwareBanned = "UPDATE %s SET banned = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateUsers = "UPDATE %s SET %s = ? WHERE %s = ?".formatted(table, hardwareIdColumn, uuidColumn);
}
@Override
protected String makeUserCols() {
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
}
@Override
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
}
private SQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException {
HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo();
hardwareInfo.hwDiskId = set.getString("hwDiskId");
hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber");
byte[] displayId = set.getBytes("displayId");
hardwareInfo.displayId = displayId == null ? null : displayId;
hardwareInfo.bitness = set.getInt("bitness");
hardwareInfo.totalMemory = set.getLong("totalMemory");
hardwareInfo.logicalProcessors = set.getInt("logicalProcessors");
hardwareInfo.physicalProcessors = set.getInt("physicalProcessors");
hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq");
hardwareInfo.battery = set.getBoolean("battery");
hardwareInfo.graphicCard = set.getString("graphicCard");
byte[] publicKey = set.getBytes("publicKey");
long id = set.getLong("id");
boolean banned = set.getBoolean("banned");
return new SQLUserHardware(hardwareInfo, publicKey, id, banned);
}
private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) throws SQLException {
PreparedStatement s = connection.prepareStatement(sqlUpdateUsers);
s.setLong(1, hwidId);
s.setString(2, uuid.toString());
s.executeUpdate();
}
@Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey);
s.setBytes(1, publicKey);
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData);
try (ResultSet set = s.executeQuery()) {
while (set.next()) {
SQLUserHardware hw = fetchHardwareInfo(set);
AuthSupportHardware.HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info);
if (result.compareLevel > criticalCompareLevel) {
connection.commit();
return hw;
} else {
connection.commit();
}
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
return null;
}
@Override
public UserHardware getHardwareInfoById(String id) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareById);
s.setLong(1, Long.parseLong(id));
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS);
s.setBytes(1, publicKey);
s.setString(2, hardwareInfo.hwDiskId);
s.setString(3, hardwareInfo.baseboardSerialNumber);
s.setBytes(4, hardwareInfo.displayId == null ? null : hardwareInfo.displayId);
s.setInt(5, hardwareInfo.bitness);
s.setLong(6, hardwareInfo.totalMemory);
s.setInt(7, hardwareInfo.logicalProcessors);
s.setInt(8, hardwareInfo.physicalProcessors);
s.setLong(9, hardwareInfo.processorMaxFreq);
s.setString(10, hardwareInfo.graphicCard);
s.setBoolean(11, hardwareInfo.battery);
s.executeUpdate();
try (ResultSet generatedKeys = s.getGeneratedKeys()) {
if (generatedKeys.next()) {
//writeHwidLog(connection, generatedKeys.getLong(1), publicKey);
long id = generatedKeys.getLong(1);
connection.commit();
return new SQLUserHardware(hardwareInfo, publicKey, id, false);
}
}
connection.commit();
return null;
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
AbstractSQLCoreProvider.SQLUserSession SQLUserSession = (AbstractSQLCoreProvider.SQLUserSession) userSession;
SQLUser SQLUser = (SQLUser) SQLUserSession.getUser();
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
if (SQLUser.hwidId == SQLUserHardware.id) return;
SQLUser.hwidId = SQLUserHardware.id;
try (Connection connection = holder.getConnection()) {
setUserHardwareId(connection, SQLUser.getUUID(), SQLUserHardware.id);
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
}
@Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.publicKey = publicKey;
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey);
s.setBytes(1, publicKey);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
connection.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
List<User> users = new LinkedList<>();
try (Connection c = holder.getConnection()) {
c.setAutoCommit(false);
PreparedStatement s = c.prepareStatement(sqlUsersByHwidId);
s.setLong(1, Long.parseLong(hardware.getId()));
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
while (!set.isLast()) {
users.add(constructUser(set));
}
}
c.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
return null;
}
return users;
}
@Override
public void banHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = true;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, true);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL Error", e);
}
}
@Override
public void unbanHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = false;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, false);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
protected AbstractSQLCoreProvider.SQLUserSession createSession(AbstractSQLCoreProvider.SQLUser user) {
return new SQLUserSession(user);
}
@Override
public UserSession extendedCheckServer(Client client, String username, String serverID) {
AbstractSQLCoreProvider.SQLUser user = (AbstractSQLCoreProvider.SQLUser) getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return createSession(user);
}
return null;
}
public class SQLUserSession extends AbstractSQLCoreProvider.SQLUserSession implements UserSessionSupportHardware {
private transient SQLUser SQLUser;
protected transient SQLUserHardware hardware;
public SQLUserSession(AbstractSQLCoreProvider.SQLUser user) {
super(user);
SQLUser = (SQLUser) user;
}
@Override
public String getHardwareId() {
return SQLUser.hwidId == 0 ? null : String.valueOf(SQLUser.hwidId);
}
@Override
public UserHardware getHardware() {
if(hardware == null) {
hardware = (SQLUserHardware) getHardwareInfoById(String.valueOf(SQLUser.hwidId));
}
return hardware;
}
}
public static class SQLUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public SQLUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "SQLUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public static class SQLUser extends AbstractSQLCoreProvider.SQLUser {
protected long hwidId;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
super(uuid, username, accessToken, serverId, password);
this.hwidId = hwidId;
}
@Override
public String toString() {
return "SQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
", hwidId=" + hwidId +
'}';
}
}
}

View file

@ -6,5 +6,5 @@
import java.io.IOException;
public interface AuthSupportExtendedCheckServer {
UserSession extendedCheckServer(Client client, String username, String serverID);
UserSession extendedCheckServer(Client client, String username, String serverID) throws IOException;
}

View file

@ -1,14 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import com.google.gson.annotations.SerializedName;
public record AccessTokenResponse(@SerializedName("access_token") String accessToken,
@SerializedName("expires_in") Long expiresIn,
@SerializedName("refresh_expires_in") Long refreshExpiresIn,
@SerializedName("refresh_token") String refreshToken,
@SerializedName("token_type") String tokenType,
@SerializedName("id_token") String idToken,
@SerializedName("not-before-policy") Integer notBeforePolicy,
@SerializedName("session_state") String sessionState,
@SerializedName("scope") String scope) {
}

View file

@ -1,178 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class OpenIDAuthCoreProvider extends AuthCoreProvider {
private transient SQLUserStore sqlUserStore;
private transient SQLServerSessionStore sqlSessionStore;
private transient OpenIDAuthenticator openIDAuthenticator;
private OpenIDConfig openIDConfig;
private HikariSQLSourceConfig sqlSourceConfig;
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return openIDAuthenticator.getDetails();
}
@Override
public User getUserByUsername(String username) {
return sqlUserStore.getByUsername(username);
}
@Override
public User getUserByUUID(UUID uuid) {
return sqlUserStore.getUserByUUID(uuid);
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
return openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
}
@Override
public AuthManager.AuthReport refreshAccessToken(String oldRefreshToken, AuthResponse.AuthContext context) {
var tokens = openIDAuthenticator.refreshAccessToken(oldRefreshToken);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new RuntimeException("invalid token", e);
}
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if (password == null) {
throw AuthException.wrongPassword();
}
var authCodePassword = (AuthCodePassword) password;
var tokens = openIDAuthenticator.authorize(authCodePassword);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
var user = openIDAuthenticator.createUserFromToken(accessToken);
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
sqlUserStore.createOrUpdateUser(user);
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new AuthException("invalid token", e);
}
if (minecraftAccess) {
var minecraftToken = generateMinecraftToken(user);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftToken, accessToken, refreshToken,
expiresIn, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
}
private String generateMinecraftToken(User user) {
return Jwts.builder()
.issuer("LaunchServer")
.subject(user.getUUID().toString())
.claim("preferred_username", user.getUsername())
.expiration(Date.from(Instant.now().plus(24, ChronoUnit.HOURS)))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
private User createUserFromMinecraftToken(String accessToken) throws AuthException {
try {
var parser = Jwts.parser()
.requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
var claims = parser.parseSignedClaims(accessToken);
var username = claims.getPayload().get("preferred_username", String.class);
var uuid = UUID.fromString(claims.getPayload().getSubject());
return new UserEntity(username, uuid, new ClientPermissions());
} catch (JwtException e) {
throw new AuthException("Bad minecraft token", e);
}
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
this.sqlSourceConfig.init();
this.sqlUserStore = new SQLUserStore(sqlSourceConfig);
this.sqlUserStore.init();
this.sqlSessionStore = new SQLServerSessionStore(sqlSourceConfig);
this.sqlSessionStore.init();
this.openIDAuthenticator = new OpenIDAuthenticator(openIDConfig);
}
@Override
public User checkServer(Client client, String username, String serverID) {
var savedServerId = sqlSessionStore.getServerIdByUsername(username);
if (!serverID.equals(savedServerId)) {
return null;
}
return sqlUserStore.getByUsername(username);
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
User user;
try {
user = createUserFromMinecraftToken(accessToken);
} catch (AuthException e) {
LogHelper.error(e);
return false;
}
if (!user.getUUID().equals(uuid)) {
return false;
}
sqlUserStore.createOrUpdateUser(user);
return sqlSessionStore.joinServer(user.getUUID(), user.getUsername(), serverID);
}
@Override
public void close() {
sqlSourceConfig.close();
}
}

View file

@ -1,232 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkSet;
import io.jsonwebtoken.security.Jwks;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.QueryHelper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.Key;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
public class OpenIDAuthenticator {
private static final HttpClient CLIENT = HttpClient.newBuilder().build();
private final OpenIDConfig openIDConfig;
private final JwtParser jwtParser;
public OpenIDAuthenticator(OpenIDConfig openIDConfig) {
this.openIDConfig = openIDConfig;
var keyLocator = loadKeyLocator(openIDConfig);
this.jwtParser = Jwts.parser()
.keyLocator(keyLocator)
.requireIssuer(openIDConfig.issuer())
.require("azp", openIDConfig.clientId())
.build();
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails() {
var state = UUID.randomUUID().toString();
var uri = QueryBuilder.get(openIDConfig.authorizationEndpoint())
.addQuery("response_type", "code")
.addQuery("client_id", openIDConfig.clientId())
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("scope", openIDConfig.scopes())
.addQuery("state", state)
.toUriString();
return List.of(new AuthWebViewDetails(uri, openIDConfig.redirectUri()));
}
public TokenResponse refreshAccessToken(String oldRefreshToken) {
var postBody = QueryBuilder.post()
.addQuery("grant_type", "refresh_token")
.addQuery("refresh_token", oldRefreshToken)
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
try {
readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new RuntimeException(e);
}
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws AuthCoreProvider.OAuthAccessTokenExpired {
Jws<Claims> token;
try {
token = readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new AuthCoreProvider.OAuthAccessTokenExpired("Can't read token", e);
}
var user = createUserFromToken(token);
long expiresIn = 0;
var expDate = token.getPayload().getExpiration();
if (expDate != null) {
expiresIn = expDate.toInstant().toEpochMilli();
}
return new OpenIDUserSession(user, accessToken, expiresIn);
}
public TokenResponse authorize(AuthCodePassword authCode) throws IOException {
var uri = URI.create(authCode.uri);
var queries = QueryHelper.splitUriQuery(uri);
String code = CommonHelper.multimapFirstOrNullValue("code", queries);
String error = CommonHelper.multimapFirstOrNullValue("error", queries);
String errorDescription = CommonHelper.multimapFirstOrNullValue("error_description", queries);
if (error != null && !error.isBlank()) {
throw new AuthException("Auth error. Error: %s, description: %s".formatted(error, errorDescription));
}
var postBody = QueryBuilder.post()
.addQuery("grant_type", "authorization_code")
.addQuery("code", code)
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
readAndVerifyToken(accessToken);
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public User createUserFromToken(String accessToken) throws AuthException {
return createUserFromToken(readAndVerifyToken(accessToken));
}
private Jws<Claims> readAndVerifyToken(String accessToken) throws AuthException {
if (accessToken == null) {
throw new AuthException("Token is null");
}
try {
return jwtParser.parseSignedClaims(accessToken);
} catch (JwtException e) {
throw new AuthException("Bad token", e);
}
}
private User createUserFromToken(Jws<Claims> token) {
var username = token.getPayload().get(openIDConfig.extractorConfig().usernameClaim(), String.class);
var uuidStr = token.getPayload().get(openIDConfig.extractorConfig().uuidClaim(), String.class);
var uuid = UUID.fromString(uuidStr);
return new UserEntity(username, uuid, new ClientPermissions());
}
private AccessTokenResponse requestToken(String postBody) {
var request = HttpRequest.newBuilder()
.uri(openIDConfig.tokenUri())
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(postBody))
.build();
HttpResponse<String> resp;
try {
resp = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return Launcher.gsonManager.gson.fromJson(resp.body(), AccessTokenResponse.class);
}
private static KeyLocator loadKeyLocator(OpenIDConfig openIDConfig) {
var request = HttpRequest.newBuilder(openIDConfig.jwksUri()).GET().build();
HttpResponse<String> response;
try {
response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
var jwks = Jwks.setParser().build().parse(response.body());
return new KeyLocator(jwks);
}
private static class KeyLocator extends LocatorAdapter<Key> {
private final Map<String, Key> keys;
public KeyLocator(JwkSet jwks) {
this.keys = jwks.getKeys().stream().collect(
Collectors.toMap(jwk -> String.valueOf(jwk.get("kid")), Jwk::toKey));
}
@Override
protected Key locate(JweHeader header) {
return super.locate(header);
}
@Override
protected Key locate(JwsHeader header) {
return keys.get(header.getKeyId());
}
@Override
protected Key doLocate(Header header) {
return super.doLocate(header);
}
}
record OpenIDUserSession(User user, String token, long expiresIn) implements UserSession {
@Override
public String getID() {
return user.getUsername();
}
@Override
public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return token;
}
@Override
public long getExpireIn() {
return expiresIn;
}
}
}

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URI;
public record OpenIDConfig(URI tokenUri, String authorizationEndpoint, String clientId, String clientSecret,
String redirectUri, URI jwksUri, String scopes, String issuer,
ClaimExtractorConfig extractorConfig) {
public record ClaimExtractorConfig(String usernameClaim, String uuidClaim) {}
}

View file

@ -1,59 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author Xakep_SDK
*/
public class QueryBuilder {
private final String uri;
private final StringBuilder query = new StringBuilder();
public QueryBuilder(String uri) {
this.uri = uri;
}
public static QueryBuilder get(String uri) {
Objects.requireNonNull(uri, "uri");
if (uri.endsWith("/")) {
uri = uri.substring(0, uri.length() - 1);
}
return new QueryBuilder(uri);
}
public static QueryBuilder post() {
return new QueryBuilder(null);
}
public QueryBuilder addQuery(String key, String value) {
if (!query.isEmpty()) {
query.append('&');
}
query.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
return this;
}
public String toUriString() {
if (uri != null) {
if (query. isEmpty()) {
return uri;
}
return uri + '?' + query;
}
return toQueryString();
}
public String toQueryString() {
return query.toString();
}
@Override
public String toString() {
return toUriString();
}
}

View file

@ -1,97 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLServerSessionStore implements ServerSessionStore {
private static final String CREATE_TABLE = """
create table if not exists `gravit_server_session` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
server_id varchar(41),
primary key (id),
unique (uuid),
unique (username)
);
""";
private static final String DELETE_SERVER_ID = """
delete from `gravit_server_session` where uuid = ?
""";
private static final String INSERT_SERVER_ID = """
insert into `gravit_server_session` (uuid, username, server_id) values (?, ?, ?)
""";
private static final String SELECT_SERVER_ID_BY_USERNAME = """
select server_id from `gravit_server_session` where username = ?
""";
private final SQLSourceConfig sqlSourceConfig;
public SQLServerSessionStore(SQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public boolean joinServer(UUID uuid, String username, String serverId) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteServerIdStmt = connection.prepareStatement(DELETE_SERVER_ID);
var insertServerIdStmt = connection.prepareStatement(INSERT_SERVER_ID)) {
deleteServerIdStmt.setString(1, uuid.toString());
deleteServerIdStmt.execute();
insertServerIdStmt.setString(1, uuid.toString());
insertServerIdStmt.setString(2, username);
insertServerIdStmt.setString(3, serverId);
insertServerIdStmt.execute();
connection.commit();
return true;
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Can't join server. Username: %s".formatted(username));
LogHelper.error(e);
}
return false;
}
@Override
public String getServerIdByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectServerId = connection.prepareStatement(SELECT_SERVER_ID_BY_USERNAME)) {
selectServerId.setString(1, username);
try (var rs = selectServerId.executeQuery()) {
if (!rs.next()) {
return null;
}
return rs.getString("server_id");
}
} catch (SQLException e) {
LogHelper.debug("Can't find server id by username. Username: %s".formatted(username));
LogHelper.error(e);
}
return null;
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createTableStmt = connection.prepareStatement(CREATE_TABLE)) {
createTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,124 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLUserStore implements UserStore {
private static final String CREATE_USER_TABLE = """
create table if not exists `gravit_user` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
primary key (id),
unique (uuid),
unique (username)
)
""";
private static final String INSERT_USER = """
insert into `gravit_user` (uuid, username) values (?, ?)
""";
private static final String DELETE_USER_BY_NAME = """
delete `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_NAME = """
select uuid, username from `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_UUID = """
select uuid, username from `gravit_user` where uuid = ?
""";
private final HikariSQLSourceConfig sqlSourceConfig;
public SQLUserStore(HikariSQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public User getByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_NAME)) {
selectUserStmt.setString(1, username);
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, username: %s".formatted(username));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_UUID)) {
selectUserStmt.setString(1, uuid.toString());
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, UUID: %s".formatted(uuid));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public void createOrUpdateUser(User user) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteUserStmt = connection.prepareStatement(DELETE_USER_BY_NAME);
var insertUserStmt = connection.prepareStatement(INSERT_USER)) {
deleteUserStmt.setString(1, user.getUsername());
deleteUserStmt.execute();
insertUserStmt.setString(1, user.getUUID().toString());
insertUserStmt.setString(2, user.getUsername());
insertUserStmt.execute();
connection.commit();
LogHelper.debug("User saved. UUID: %s, username: %s".formatted(user.getUUID(), user.getUsername()));
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Failed to save user");
LogHelper.error(e);
throw new RuntimeException("Failed to save user", e);
}
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createUserTableStmt = connection.prepareStatement(CREATE_USER_TABLE)) {
createUserTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,8 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.util.UUID;
public interface ServerSessionStore {
boolean joinServer(UUID uuid, String username, String serverId);
String getServerIdByUsername(String username);
}

View file

@ -1,5 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
public record TokenResponse(String accessToken, long accessTokenExpiresIn,
String refreshToken, long refreshTokenExpiresIn) {
}

View file

@ -1,23 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
record UserEntity(String username, UUID uuid, ClientPermissions permissions) implements User {
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
}

View file

@ -1,13 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
public interface UserStore {
User getByUsername(String username);
User getUserByUUID(UUID uuid);
void createOrUpdateUser(User user);
}

View file

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

View file

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

View file

@ -1,186 +0,0 @@
package pro.gravit.launchserver.auth.updates;
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.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
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";
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);
}
@Override
public void init(LaunchServer server) {
super.init(server);
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
} catch (IOException e) {
logger.error("Updates not synced", 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<>();
for(var e : files) {
map.put(e, path.resolve(e));
}
return map;
}
@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);
}
}
@Override
public void delete(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
IOHelper.deleteDir(path, true);
}
@Override
public void create(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
Files.createDirectories(path);
}
}

View file

@ -1,52 +0,0 @@
package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
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;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalUpdatesProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public void sync() throws IOException {
sync(null);
}
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() {
}
}

View file

@ -4,7 +4,9 @@
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@ -12,11 +14,11 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class BinaryPipeline {
public class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final Path buildDir;
public final String nameFormat;
protected transient final Logger logger = LogManager.getLogger();
private transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir;
@ -78,6 +80,27 @@ public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pr
return Optional.empty();
}
public void build(Path target, boolean deleteTempFiles) 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);
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();
if (deleteTempFiles) IOHelper.move(thisPath, target);
else IOHelper.copy(thisPath, target);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
}
public String nextName(String taskName) {
return nameFormat.formatted(taskName);
}

View file

@ -1,14 +1,11 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server;
@ -22,27 +19,11 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
}
public static Path resolve(LaunchServer server, String ext) {
return Path.of(server.config.binaryName + ext);
return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
}
public void 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);
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);
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
}
public final boolean exists() {
@ -56,14 +37,10 @@ public final byte[] getDigest() {
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;
}
public final boolean sync() throws IOException {
boolean exists = exists();
digest = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(syncBinaryFile)) : null;
return exists;
}
}

View file

@ -7,7 +7,10 @@
import pro.gravit.utils.helper.UnpackHelper;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.stream.Collectors;
@ -34,10 +37,8 @@ public Path process(Path inputFile) throws IOException {
server.launcherBinary.addonLibs.clear();
server.launcherBinary.files.clear();
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false);
if(Files.isDirectory(server.launcherLibrariesCompile)) {
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
}
try(Stream<Path> stream = Files.walk(server.launcherPack, FileVisitOption.FOLLOW_LINKS).filter((e) -> {
try(Stream<Path> stream = Files.walk(server.launcherPack).filter((e) -> {
try {
return !Files.isDirectory(e) && !Files.isHidden(e);
} catch (IOException ex) {

View file

@ -32,7 +32,6 @@ public void invoke(String... args) throws Exception {
boolean value = Boolean.parseBoolean(args[0]);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
config.getWatchManager().setIntervalSeconds(-1);
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
ctx.updateLoggers();

View file

@ -5,7 +5,6 @@
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;
@ -13,6 +12,7 @@
import pro.gravit.utils.command.CommandException;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
@ -61,11 +61,9 @@ public void invoke(String... args) throws IOException, CommandException {
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();
clientProfile.setTitle(dirName);
clientProfile.setDir(dirName);
clientProfile.setUUID(UUID.randomUUID());
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
@ -96,7 +94,10 @@ public void invoke(String... args) throws IOException, CommandException {
isMirrorClientDownload = true;
}
}
server.config.profileProvider.addProfile(clientProfile);
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
dirName, "json"))) {
Launcher.gsonManager.configGson.toJson(clientProfile, writer);
}
// Finished
server.syncProfilesDir();

View file

@ -2,13 +2,15 @@
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.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@ -23,7 +25,7 @@ public CloneProfileCommand(LaunchServer server) {
@Override
public String getArgsDescription() {
return "[profile title/uuid] [new profile title]";
return "[profile file name] [new profile title]";
}
@Override
@ -34,16 +36,16 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
ClientProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
var profilePath = server.profilesDir.resolve(args[0].concat(".json"));
if(!Files.exists(profilePath)) {
logger.error("File {} not found", profilePath);
}
var builder = new ClientProfileBuilder(profile);
builder.setTitle(args[1]);
builder.setUuid(UUID.randomUUID());
ClientProfile profile;
try(Reader reader = IOHelper.newReader(profilePath)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.setTitle(args[1]);
profile.setUUID(UUID.randomUUID());
if(profile.getServers().size() == 1) {
profile.getServers().getFirst().name = args[1];
}
@ -59,9 +61,11 @@ public void invoke(String... args) throws Exception {
}
});
}
builder.setDir(args[1]);
profile = builder.createClientProfile();
server.config.profileProvider.addProfile(profile);
profile.setDir(args[1]);
var targetPath = server.profilesDir.resolve(args[1].concat(".json"));
try(Writer writer = IOHelper.newWriter(targetPath)) {
Launcher.gsonManager.gson.toJson(profile, writer);
}
logger.info("Profile {} cloned from {}", args[1], args[0]);
server.syncProfilesDir();
server.syncUpdatesDir(List.of(args[1]));

View file

@ -5,8 +5,9 @@
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.util.UUID;
import java.nio.file.Files;
public class DeleteProfileCommand extends Command {
private final transient Logger logger = LogManager.getLogger(ListProfilesCommand.class);
@ -27,24 +28,29 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
ClientProfile profile;
try {
UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]);
ClientProfile profile = null;
for(var p : server.getProfiles()) {
if(p.getUUID().toString().equals(args[0]) || p.getTitle().equals(args[0])) {
profile = p;
break;
}
}
if(profile == null) {
logger.error("Profile {} not found", args[0]);
return;
}
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir());
var clientDir = server.updatesDir.resolve(profile.getDir()).toAbsolutePath();
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", clientDir);
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 {}", clientDir);
IOHelper.deleteDir(clientDir, true);
var profileFile = profile.getProfileFilePath();
if(profileFile == null) {
profileFile = server.profilesDir.resolve(profile.getTitle().concat(".json"));
}
logger.info("Delete {}", profileFile);
Files.deleteIfExists(profileFile);
}
}

View file

@ -22,7 +22,7 @@ public String getUsageDescription() {
}
@Override
public void invoke(String... args) {
public void invoke(String... args) throws Exception {
for(var profile : server.getProfiles()) {
logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : "");
}

View file

@ -2,10 +2,14 @@
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.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
public class MakeProfileCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
@ -33,7 +37,9 @@ public void invoke(String... args) throws Exception {
logger.info("Detected option {}", option);
}
ClientProfile profile = MakeProfileHelper.makeProfile(version, args[0], options);
server.config.profileProvider.addProfile(profile);
try (Writer writer = IOHelper.newWriter(server.profilesDir.resolve(args[0].concat(".json")))) {
Launcher.gsonManager.configGson.toJson(profile, writer);
}
logger.info("Profile {} created", args[0]);
server.syncProfilesDir();
}

View file

@ -2,10 +2,17 @@
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.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
public class SaveProfilesCommand extends Command {
@ -15,6 +22,21 @@ public SaveProfilesCommand(LaunchServer server) {
super(server);
}
public static void saveProfile(ClientProfile profile, Path path) throws IOException {
if (profile.getUUID() == null) profile.setUUID(UUID.randomUUID());
if (profile.getServers().isEmpty()) {
ClientProfile.ServerProfile serverProfile = new ClientProfile.ServerProfile();
serverProfile.isDefault = true;
serverProfile.name = profile.getTitle();
serverProfile.serverAddress = profile.getServerAddress();
serverProfile.serverPort = profile.getServerPort();
profile.getServers().add(serverProfile);
}
try (Writer w = IOHelper.newWriter(path)) {
Launcher.gsonManager.configGson.toJson(profile, w);
}
}
@Override
public String getArgsDescription() {
return "[profile names...]";
@ -30,14 +52,17 @@ 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);
Path profilePath = server.profilesDir.resolve(profileName.concat(".json"));
if (!Files.exists(profilePath)) {
logger.error("Profile {} not found", profilePath.toString());
return;
}
server.config.profileProvider.addProfile(profile);
ClientProfile profile;
try (Reader reader = IOHelper.newReader(profilePath)) {
profile = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
}
saveProfile(profile, profilePath);
logger.info("Profile {} save successful", profilePath.toString());
}
server.syncProfilesDir();
}

View file

@ -117,7 +117,7 @@ public void invoke(String... args) throws Exception {
Files.deleteIfExists(proguardConf.mappings);
}
});
return commands;
return null;
}
public static class ProGuardMultiReleaseFixer implements LauncherBuildTask {
@ -213,7 +213,7 @@ public Path process(Path inputFile) throws IOException {
args.add(IOHelper.resolveJavaBin(IOHelper.JVM_DIR).toAbsolutePath().toString());
args.addAll(component.jvmArgs);
args.add("-cp");
try(Stream<Path> files = Files.walk(server.librariesDir, FileVisitOption.FOLLOW_LINKS)) {
try(Stream<Path> files = Files.walk(Path.of("libraries"), FileVisitOption.FOLLOW_LINKS)) {
args.add(files
.filter(e -> e.getFileName().toString().endsWith(".jar"))
.map(path -> path.toAbsolutePath().toString())

View file

@ -1,5 +1,6 @@
package pro.gravit.launchserver.config;
import io.netty.channel.epoll.Epoll;
import io.netty.handler.logging.LogLevel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -8,17 +9,12 @@
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.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.StdProtectHandler;
import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
import pro.gravit.launchserver.auth.updates.LocalUpdatesProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.AuthLimiterComponent;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.components.ProGuardComponent;
import pro.gravit.launchserver.socket.NettyObjectFactory;
import java.util.*;
@ -34,13 +30,12 @@ public final class LaunchServerConfig {
public String[] mirrors;
public String binaryName;
public boolean copyBinaries = true;
public boolean cacheUpdates = true;
public LauncherConfig.LauncherEnvironment env;
public Map<String, AuthProviderPair> auth;
// Handlers & Providers
public ProtectHandler protectHandler;
public Map<String, Component> components;
public ProfileProvider profileProvider = new LocalProfileProvider();
public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
public NettyConfig netty;
public LauncherConf launcher;
public JarSignerConf sign;
@ -64,7 +59,12 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
newConfig.netty.fileServerEnabled = true;
newConfig.netty.binds = new NettyBindAddress[]{new NettyBindAddress("0.0.0.0", 9274)};
newConfig.netty.performance = new NettyPerformanceConfig();
newConfig.netty.performance.mode = NettyObjectFactory.NettyFactoryMode.AUTO;
try {
newConfig.netty.performance.usingEpoll = Epoll.isAvailable();
} catch (Throwable e) {
// Epoll class line 51+ catch (Exception) but Error will be thrown by System.load
newConfig.netty.performance.usingEpoll = false;
} // such as on ARM
newConfig.netty.performance.bossThread = 2;
newConfig.netty.performance.workerThread = 8;
newConfig.netty.performance.schedulerThread = 2;
@ -85,7 +85,6 @@ 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();
return newConfig;
}
@ -167,14 +166,6 @@ public void init(LaunchServer.ReloadType type) {
server.registerObject("protectHandler", protectHandler);
protectHandler.init(server);
}
if(profileProvider != null) {
server.registerObject("profileProvider", profileProvider);
profileProvider.init(server);
}
if(updatesProvider != null) {
server.registerObject("updatesProvider", updatesProvider);
updatesProvider.init(server);
}
if (components != null) {
components.forEach((k, v) -> server.registerObject("component.".concat(k), v));
}
@ -215,14 +206,6 @@ public void close(LaunchServer.ReloadType type) {
server.unregisterObject("protectHandler", protectHandler);
protectHandler.close();
}
if(profileProvider != null) {
server.unregisterObject("profileProvider", profileProvider);
profileProvider.close();
}
if(updatesProvider != null) {
server.unregisterObject("updatesProvider", updatesProvider);
updatesProvider.close();
}
}
public static class JarSignerConf {
@ -272,7 +255,7 @@ public static class NettyConfig {
}
public static class NettyPerformanceConfig {
public NettyObjectFactory.NettyFactoryMode mode = NettyObjectFactory.NettyFactoryMode.AUTO;
public boolean usingEpoll;
public int bossThread;
public int workerThread;
public int schedulerThread;

View file

@ -49,6 +49,7 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
// Official Mojang launcher java arguments
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_12_2) <= 0) {
// lwjgl3ify arguments https://github.com/GTNewHorizons/lwjgl3ify
jvmArgs.add("-Djava.security.manager=allow");
jvmArgs.add("--add-opens");
jvmArgs.add("java.base/jdk.internal.loader=ALL-UNNAMED");
jvmArgs.add("--add-opens");
@ -102,7 +103,6 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
}
if (fabric.isPresent()) {
builder.setAltClassPath(fabric.orElseThrow().getAltClassPath());
jvmArgs.add("-Dsodium.checks.issue2561=false"); // Please don't check LWJL3 version (Sodium: https://github.com/CaffeineMC/sodium-fabric/issues/2561 )
}
if(quilt.isPresent()) {
builder.setClassLoaderConfig(ClientProfile.ClassLoaderConfig.SYSTEM_ARGS);
@ -203,7 +203,7 @@ public static String getMainClassByVersion(ClientProfile.Version version, MakePr
return "cpw.mods.modlauncher.Launcher";
}
if (findOption(options, MakeProfileOptionFabric.class).isPresent()) {
return "net.fabricmc.loader.impl.launch.knot.KnotClient";
return "net.fabricmc.loader.launch.knot.KnotClient";
}
if(findOption(options, MakeProfilesOptionsQuilt.class).isPresent()) {
return "org.quiltmc.loader.impl.launch.knot.KnotClient";

View file

@ -33,7 +33,7 @@ public class LauncherModuleLoader {
public LauncherModuleLoader(LaunchServer server) {
this.server = server;
modulesDir = server.launcherModulesDir;
modulesDir = server.dir.resolve("launcher-modules");
}
public void init() {

View file

@ -29,7 +29,6 @@
public class CertificateManager {
private transient final Logger logger = LogManager.getLogger();
public LauncherTrustManager trustManager;
private Path truststorePath;
public void writePrivateKey(Path file, PrivateKey privateKey) throws IOException {
writePrivateKey(IOHelper.newWriter(file), privateKey);
@ -92,7 +91,6 @@ public X509CertificateHolder readCertificate(Reader reader) throws IOException {
}
public void readTrustStore(Path dir) throws IOException, CertificateException {
this.truststorePath = dir;
if (!IOHelper.isDir(dir)) {
Files.createDirectories(dir);
try {
@ -133,8 +131,4 @@ public LauncherTrustManager.CheckClassResult checkClass(Class<?> clazz) {
X509Certificate[] certificates = JVMHelper.getCertificates(clazz);
return trustManager.checkCertificates(certificates, trustManager::stdCertificateChecker);
}
public Path getTruststorePath() {
return truststorePath;
}
}

View file

@ -22,7 +22,6 @@ public class KeyAgreementManager {
public final RSAPublicKey rsaPublicKey;
public final RSAPrivateKey rsaPrivateKey;
public final String legacySalt;
public final Path keyDirectory;
public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivateKey, RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey, String legacySalt) {
this.ecdsaPublicKey = ecdsaPublicKey;
@ -30,11 +29,9 @@ public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivate
this.rsaPublicKey = rsaPublicKey;
this.rsaPrivateKey = rsaPrivateKey;
this.legacySalt = legacySalt;
this.keyDirectory = null;
}
public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpecException {
this.keyDirectory = keyDirectory;
Path ecdsaPublicKeyPath = keyDirectory.resolve("ecdsa_id.pub"), ecdsaPrivateKeyPath = keyDirectory.resolve("ecdsa_id");
Logger logger = LogManager.getLogger();
if (IOHelper.isFile(ecdsaPublicKeyPath) && IOHelper.isFile(ecdsaPrivateKeyPath)) {

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.manangers;
import com.google.gson.GsonBuilder;
import marcono1234.gson.recordadapter.RecordTypeAdapterFactory;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.managers.GsonManager;
import pro.gravit.launcher.base.modules.events.PreGsonPhase;
@ -14,10 +15,8 @@
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.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.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.WebSocketService;
@ -35,6 +34,9 @@ public LaunchServerGsonManager(LaunchServerModulesManager modulesManager) {
@Override
public void registerAdapters(GsonBuilder builder) {
super.registerAdapters(builder);
builder.registerTypeAdapterFactory(RecordTypeAdapterFactory.builder()
.allowMissingComponentValues()
.create());
builder.registerTypeAdapter(ClientProfile.Version.class, new ClientProfile.Version.GsonSerializer());
builder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers));
builder.registerTypeAdapter(AuthCoreProvider.class, new UniversalJsonAdapter<>(AuthCoreProvider.providers));
@ -48,8 +50,6 @@ 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(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
modulesManager.invokeEvent(new PreGsonPhase(builder));
//ClientWebSocketService.appendTypeAdapters(builder);
}

View file

@ -1,45 +1,129 @@
package pro.gravit.launchserver.manangers;
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.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Stream;
public class UpdatesManager {
private final LaunchServer server;
private final Logger logger = LogManager.getLogger();
private final Path cacheFile;
private volatile Map<String, HashedDir> updatesDirMap;
public UpdatesManager(LaunchServer server) {
this.server = server;
this.cacheFile = server.dir.resolve(".updates-cache");
}
@Deprecated
public void readUpdatesFromCache() {
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());
}
@Deprecated
public void readUpdatesDir() {
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(cacheFile);
}
public void readUpdatesDir() throws IOException {
if (server.config.cacheUpdates) {
if (Files.exists(cacheFile)) {
try {
readCache(cacheFile);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
syncUpdatesDir(null);
}
@Deprecated
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
server.config.updatesProvider.sync(dirs);
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(server.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 (server.config.cacheUpdates) {
try {
writeCache(cacheFile);
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
@Deprecated
public HashSet<String> getUpdatesList() {
return new HashSet<>();
HashSet<String> set = new HashSet<>();
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet())
set.add(entry.getKey());
return set;
}
@Deprecated
public HashedDir getUpdate(String name) {
return server.config.updatesProvider.getUpdatesDir(name);
return updatesDirMap.get(name);
}
@Deprecated
public void addUpdate(String name, HashedDir dir) {
throw new UnsupportedOperationException();
updatesDirMap.put(name, dir);
}
}

View file

@ -4,6 +4,7 @@
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
@ -35,9 +36,14 @@ public class LauncherNettyServer implements AutoCloseable {
public LauncherNettyServer(LaunchServer server) {
LaunchServerConfig.NettyConfig config = server.config.netty;
NettyObjectFactory.setMode(config.performance.mode);
NettyObjectFactory.setUsingEpoll(config.performance.usingEpoll);
Logger logger = LogManager.getLogger();
logger.info("Netty usage {} transport mode", NettyObjectFactory.getMode());
if (config.performance.usingEpoll) {
logger.debug("Netty: Epoll enabled");
}
if (config.performance.usingEpoll && !Epoll.isAvailable()) {
logger.error("Epoll is not available: (netty,perfomance.usingEpoll configured wrongly)", Epoll.unavailabilityCause());
}
bossGroup = NettyObjectFactory.newEventLoopGroup(config.performance.bossThread, "LauncherNettyServer.bossGroup");
workerGroup = NettyObjectFactory.newEventLoopGroup(config.performance.workerThread, "LauncherNettyServer.workerGroup");
serverBootstrap = new ServerBootstrap();

View file

@ -2,58 +2,30 @@
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultiThreadIoEventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.uring.IoUring;
import io.netty.channel.uring.IoUringIoHandler;
import io.netty.channel.uring.IoUringServerSocketChannel;
public class NettyObjectFactory {
private static NettyFactoryMode mode;
private static boolean epoll = false;
public static void setMode(NettyFactoryMode mode) {
NettyObjectFactory.mode = mode;
if(mode == NettyFactoryMode.AUTO) {
if(IoUring.isAvailable()) {
NettyObjectFactory.mode = NettyFactoryMode.IO_URING;
return;
}
if(Epoll.isAvailable()) {
NettyObjectFactory.mode = NettyFactoryMode.EPOLL;
return;
}
NettyObjectFactory.mode = NettyFactoryMode.NIO;
}
}
public static NettyFactoryMode getMode() {
return mode;
public static void setUsingEpoll(boolean value) {
epoll = value;
}
public static EventLoopGroup newEventLoopGroup(int threads, String poolName) {
return switch (mode) {
case AUTO -> null;
case NIO -> new MultiThreadIoEventLoopGroup(threads, NioIoHandler.newFactory());
case EPOLL -> new MultiThreadIoEventLoopGroup(threads, EpollIoHandler.newFactory());
case IO_URING -> new MultiThreadIoEventLoopGroup(threads, IoUringIoHandler.newFactory());
};
if (epoll)
return new EpollEventLoopGroup(threads);
else
return new NioEventLoopGroup(threads);
}
public static ChannelFactory<? extends ServerChannel> getServerSocketChannelFactory() {
return switch (mode) {
case AUTO -> null;
case NIO -> NioServerSocketChannel::new;
case EPOLL -> EpollServerSocketChannel::new;
case IO_URING -> IoUringServerSocketChannel::new;
};
}
public enum NettyFactoryMode {
AUTO, NIO, EPOLL, IO_URING
if (epoll)
return EpollServerSocketChannel::new;
else
return NioServerSocketChannel::new;
}
}

View file

@ -1,94 +0,0 @@
package pro.gravit.launchserver.socket;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.config.log4j.LogAppender;
import pro.gravit.utils.command.CommandHandler;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class SocketCommandServer implements Runnable {
private final Logger logger = LogManager.getLogger(SocketCommandServer.class);
private ServerSocketChannel channel;
private Path path;
private UnixDomainSocketAddress address;
private ServerSocketChannel serverChannel;
private CommandHandler commandHandler;
private transient SocketChannel clientChannel;
public SocketCommandServer(CommandHandler commandHandler, Path path) {
this.commandHandler = commandHandler;
this.path = path;
}
private void runCommand(SocketChannel channel, String command) {
logger.info("Command '{}' from socket", command);
clientChannel = channel;
try {
commandHandler.evalNative(command, false);
} catch (Throwable e) {
logger.error("Error when execute command", e);
} finally {
clientChannel = null;
}
}
@Override
public void run() {
try {
Files.deleteIfExists(path);
this.address = UnixDomainSocketAddress.of(path);
serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
serverChannel.configureBlocking(true);
serverChannel.bind(address);
LogAppender.getInstance().addListener((logEvent -> {
if(clientChannel != null && clientChannel.isOpen()) {
try {
String s = logEvent.getMessage().getFormattedMessage()+"\n";
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
ByteBuffer buffer = ByteBuffer.wrap(bytes);
clientChannel.write(buffer);
} catch (Throwable ignored) {
}
}
}));
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
SocketChannel channel = serverChannel.accept();
try (channel) {
channel.configureBlocking(true);
String command = null;
mark:
while (true) {
int bytesRead = channel.read(buffer);
if (bytesRead < 0) {
break;
}
for (var i = 0; i < buffer.limit(); i++) {
if (buffer.get(i) == '\n') {
command = new String(buffer.array(), 0, i);
break mark;
}
}
}
if (command != null) {
runCommand(channel, command);
}
} finally {
buffer.clear();
}
}
} catch (Throwable e) {
logger.error("Unix command socket server error", e);
}
}
}

View file

@ -22,6 +22,7 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.Arrays;

View file

@ -7,7 +7,7 @@
public interface WebSocketServerResponse extends WebSocketRequest {
String getType();
void execute(ChannelHandlerContext ctx, Client client);
void execute(ChannelHandlerContext ctx, Client client) throws Exception;
default ThreadSafeStatus getThreadSafeStatus() {
return ThreadSafeStatus.READ;

View file

@ -23,7 +23,7 @@ public String getType() {
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
sendResult(new CurrentUserRequestEvent(collectUserInfoFromClient(server, client)));
}
}

View file

@ -13,7 +13,6 @@
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();
@ -41,6 +40,6 @@ public void execute(ChannelHandlerContext ctx, Client client) {
sendError("Access denied");
return;
}
sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client)));
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
}
}

View file

@ -44,7 +44,7 @@ public String getType() {
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
if (accessToken == null && !client.isAuth && needUserInfo) {
sendError("Invalid request");
return;

View file

@ -12,7 +12,7 @@ public String getType() {
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
if(!client.isAuth || client.auth == null || client.getUser() == null) {
sendError("Access denied");
return;

View file

@ -14,7 +14,7 @@ public String getType() {
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
if(!client.isAuth || client.auth == null || client.getUser() == null) {
sendError("Access denied");
return;

View file

@ -12,7 +12,7 @@ public String getType() {
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
sendResult(new GetConnectUUIDRequestEvent(connectUUID, server.shardId));
}
}

View file

@ -52,7 +52,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
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));
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
}
} else if (launcher_type == 2) //EXE
{
@ -62,7 +62,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0));
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
}
} else sendError("Request launcher type error");
}

View file

@ -1,6 +1,7 @@
apply plugin: 'com.github.johnrengelman.shadow'
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
String mainAgentName = "pro.gravit.launcher.runtime.LauncherAgent"
repositories {
maven {
@ -19,6 +20,7 @@
jar {
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Multi-Release": "true",
"Automatic-Module-Name": "GravitLauncher")
}

View file

@ -124,6 +124,7 @@ public static void main(String... args) throws Throwable {
LogHelper.printLicense("Launcher");
LauncherEngine.checkClass(LauncherEngineWrapper.class);
LauncherEngine.checkClass(LauncherEngine.class);
LauncherEngine.checkClass(LauncherAgent.class);
LauncherEngine.checkClass(ClientLauncherEntryPoint.class);
LauncherEngine.modulesManager = new RuntimeModuleManager();
LauncherEngine.modulesManager.loadModule(new RuntimeLauncherCoreModule());

View file

@ -28,6 +28,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
public class ClientLauncherProcess {
public final List<String> pre = new LinkedList<>();
@ -108,14 +109,14 @@ public static String getPathSeparator() {
private void applyClientProfile() {
this.systemClassPath.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString());
this.jvmArgs.addAll(this.params.profile.getJvmArgs());
Collections.addAll(this.jvmArgs, this.params.profile.getJvmArgs());
for (OptionalAction a : this.params.actions) {
if (a instanceof OptionalActionJvmArgs) {
this.jvmArgs.addAll(((OptionalActionJvmArgs) a).args);
}
}
this.systemEnv.put("JAVA_HOME", javaVersion.jvmDir.toString());
this.systemClassPath.addAll(this.params.profile.getAlternativeClassPath());
Collections.addAll(this.systemClassPath, this.params.profile.getAlternativeClassPath());
if (params.ram > 0) {
this.jvmArgs.add("-Xmx" + params.ram + 'M');
}
@ -127,6 +128,8 @@ private void applyClientProfile() {
this.params.oauthExpiredTime = Request.getTokenExpiredTime();
this.params.extendedTokens = Request.getExtendedTokens();
}
this.jvmModules.addAll(this.params.profile.getModules());
this.jvmModulesPaths.addAll(this.params.profile.getModulePath());
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderCreateEvent(this));
}
@ -141,41 +144,11 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
}
//ADD CLASSPATH
processArgs.add(JVMHelper.jvmProperty("java.library.path", this.params.nativesDir));
if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
Set<Path> ignorePath = new HashSet<>();
var moduleConf = params.profile.getModuleConf();
if(moduleConf != null) {
if(moduleConf.modulePath != null && !moduleConf.modulePath.isEmpty()) {
processArgs.add("-p");
for(var e : moduleConf.modulePath) {
ignorePath.add(Path.of(e));
}
processArgs.add(String.join(File.pathSeparator, moduleConf.modulePath));
}
if(moduleConf.modules != null && !moduleConf.modules.isEmpty()) {
processArgs.add("--add-modules");
processArgs.add(String.join(",", moduleConf.modules));
}
if(moduleConf.exports != null && !moduleConf.exports.isEmpty()) {
for(var e : moduleConf.exports.entrySet()) {
processArgs.add("--add-exports");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
if(moduleConf.opens != null && !moduleConf.opens.isEmpty()) {
for(var e : moduleConf.opens.entrySet()) {
processArgs.add("--add-opens");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
if(moduleConf.reads != null && !moduleConf.reads.isEmpty()) {
for(var e : moduleConf.reads.entrySet()) {
processArgs.add("--add-reads");
processArgs.add(String.format("%s=%s", e.getKey(), e.getValue()));
}
}
}
systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(ignorePath, workDir, params.actions, params.profile)
if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.AGENT) {
processArgs.add("-javaagent:".concat(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString()));
} else if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(new HashSet<>(), workDir, params.actions, params.profile)
.filter(x -> !params.profile.getModulePath().contains(workDir.relativize(x).toString()))
.map(Path::toString)
.toList());
}

View file

@ -141,6 +141,7 @@ public void cancel() {
}
}
tasks.clear();
executor.shutdownNow();
}
public boolean isCanceled() {
@ -255,7 +256,7 @@ protected HttpRequest makeHttpRequest(URI baseUri, String filePath) throws URISy
}
protected ProgressTrackingBodyHandler<Path> makeBodyHandler(Path file, DownloadCallback callback) {
return new ProgressTrackingBodyHandler<>(HttpResponse.BodyHandlers.ofFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING), callback);
return new ProgressTrackingBodyHandler<>(HttpResponse.BodyHandlers.ofFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE), callback);
}
public interface DownloadCallback {

View file

@ -12,11 +12,13 @@
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.*;
public final class ClientProfile implements Comparable<ClientProfile> {
private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher(
new String[0], new String[]{"indexes", "objects"}, new String[0]);
private transient Path profileFilePath;
@LauncherNetworkAPI
private String title;
@LauncherNetworkAPI
@ -39,6 +41,8 @@ public final class ClientProfile implements Comparable<ClientProfile> {
@LauncherNetworkAPI
private List<String> updateExclusions;
@LauncherNetworkAPI
private List<String> updateShared;
@LauncherNetworkAPI
private List<String> updateVerify;
@LauncherNetworkAPI
private Set<OptionalFile> updateOptional;
@ -47,6 +51,10 @@ public final class ClientProfile implements Comparable<ClientProfile> {
@LauncherNetworkAPI
private List<String> classPath;
@LauncherNetworkAPI
private List<String> modulePath = new ArrayList<>();
@LauncherNetworkAPI
private List<String> modules = new ArrayList<>();
@LauncherNetworkAPI
private List<String> altClassPath;
@LauncherNetworkAPI
private List<String> clientArgs;
@ -81,37 +89,54 @@ public final class ClientProfile implements Comparable<ClientProfile> {
@LauncherNetworkAPI
private LaunchOptions.ModuleConf moduleConf;
public ClientProfile(String title, UUID uuid, Version version, String info, String dir, int sortIndex, String assetIndex, String assetDir, List<String> update, List<String> updateExclusions, List<String> updateVerify, Set<OptionalFile> updateOptional, List<String> jvmArgs, List<String> classPath, List<String> altClassPath, List<String> clientArgs, List<String> compatClasses, List<String> loadNatives, Map<String, String> properties, List<ServerProfile> servers, ClassLoaderConfig classLoaderConfig, List<CompatibilityFlags> flags, int recommendJavaVersion, int minJavaVersion, int maxJavaVersion, ProfileDefaultSettings settings, boolean limited, String mainClass, String mainModule, LaunchOptions.ModuleConf moduleConf) {
this.title = title;
this.uuid = uuid;
this.version = version;
this.info = info;
this.dir = dir;
this.sortIndex = sortIndex;
this.assetIndex = assetIndex;
this.assetDir = assetDir;
public ClientProfile() {
update = new ArrayList<>();
updateExclusions = new ArrayList<>();
updateShared = new ArrayList<>();
updateVerify = new ArrayList<>();
updateOptional = new HashSet<>();
jvmArgs = new ArrayList<>();
classPath = new ArrayList<>();
modulePath = new ArrayList<>();
altClassPath = new ArrayList<>();
clientArgs = new ArrayList<>();
compatClasses = new ArrayList<>();
properties = new HashMap<>();
servers = new ArrayList<>(1);
classLoaderConfig = ClassLoaderConfig.LAUNCHER;
flags = new ArrayList<>();
}
public ClientProfile(List<String> update, List<String> updateExclusions, List<String> updateShared, List<String> updateVerify, Set<OptionalFile> updateOptional, List<String> jvmArgs, List<String> classPath, List<String> modulePath, List<String> modules, List<String> altClassPath, List<String> clientArgs, List<String> compatClasses, Map<String, String> properties, List<ServerProfile> servers, ClassLoaderConfig classLoaderConfig, List<CompatibilityFlags> flags, Version version, String assetIndex, String dir, String assetDir, int recommendJavaVersion, int minJavaVersion, int maxJavaVersion, ProfileDefaultSettings settings, int sortIndex, UUID uuid, String title, String info, String mainClass) {
this.update = update;
this.updateExclusions = updateExclusions;
this.updateShared = updateShared;
this.updateVerify = updateVerify;
this.updateOptional = updateOptional;
this.jvmArgs = jvmArgs;
this.classPath = classPath;
this.modulePath = modulePath;
this.modules = modules;
this.altClassPath = altClassPath;
this.clientArgs = clientArgs;
this.compatClasses = compatClasses;
this.loadNatives = loadNatives;
this.properties = properties;
this.servers = servers;
this.classLoaderConfig = classLoaderConfig;
this.flags = flags;
this.version = version;
this.assetIndex = assetIndex;
this.dir = dir;
this.assetDir = assetDir;
this.recommendJavaVersion = recommendJavaVersion;
this.minJavaVersion = minJavaVersion;
this.maxJavaVersion = maxJavaVersion;
this.settings = settings;
this.limited = limited;
this.sortIndex = sortIndex;
this.uuid = uuid;
this.title = title;
this.info = info;
this.mainClass = mainClass;
this.mainModule = mainModule;
this.moduleConf = moduleConf;
this.flags = flags;
}
public ServerProfile getDefaultServerProfile() {
@ -134,22 +159,34 @@ public FileNameMatcher getAssetUpdateMatcher() {
return getVersion().compareTo(ClientProfileVersions.MINECRAFT_1_7_10) >= 0 ? ASSET_MATCHER : null;
}
public List<String> getClassPath() {
return Collections.unmodifiableList(classPath);
public String[] getClassPath() {
return classPath.toArray(new String[0]);
}
public List<String> getAlternativeClassPath() {
return Collections.unmodifiableList(altClassPath);
public List<String> getModulePath() {
return Collections.unmodifiableList(modulePath);
}
public List<String> getClientArgs() {
return Collections.unmodifiableList(clientArgs);
public List<String> getModules() {
return Collections.unmodifiableList(modules);
}
public String[] getAlternativeClassPath() {
return altClassPath.toArray(new String[0]);
}
public String[] getClientArgs() {
return clientArgs.toArray(new String[0]);
}
public String getDir() {
return dir;
}
public void setDir(String dir) {
this.dir = dir;
}
public String getAssetDir() {
return assetDir;
}
@ -158,25 +195,24 @@ public List<String> getUpdateExclusions() {
return Collections.unmodifiableList(updateExclusions);
}
public List<String> getUpdate() {
return Collections.unmodifiableList(update);
}
public List<String> getUpdateVerify() {
return Collections.unmodifiableList(updateVerify);
}
public FileNameMatcher getClientUpdateMatcher() {
public FileNameMatcher getClientUpdateMatcher(/*boolean excludeOptional*/) {
String[] updateArray = update.toArray(new String[0]);
String[] verifyArray = updateVerify.toArray(new String[0]);
List<String> excludeList;
//if(excludeOptional)
//{
// excludeList = new ArrayList<>();
// excludeList.addAll(updateExclusions);
// excludeList.addAll(updateOptional);
//}
//else
excludeList = updateExclusions;
String[] exclusionsArray = excludeList.toArray(new String[0]);
return new FileNameMatcher(updateArray, verifyArray, exclusionsArray);
}
public List<String> getJvmArgs() {
return Collections.unmodifiableList(jvmArgs);
public String[] getJvmArgs() {
return jvmArgs.toArray(new String[0]);
}
public String getMainClass() {
@ -253,6 +289,10 @@ public OptionalFile getOptionalFile(String file) {
return null;
}
public Collection<String> getShared() {
return updateShared;
}
public int getServerPort() {
ServerProfile profile = getDefaultServerProfile();
return profile == null ? 25565 : profile.serverPort;
@ -266,14 +306,26 @@ public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
@Deprecated
public boolean isUpdateFastCheck() {
return true;
@ -288,6 +340,10 @@ public UUID getUUID() {
return uuid;
}
public void setUUID(UUID uuid) {
this.uuid = uuid;
}
public boolean hasFlag(CompatibilityFlags flag) {
return flags.contains(flag);
}
@ -357,6 +413,18 @@ public String getProperty(String name) {
return properties.get(name);
}
public void putProperty(String name, String value) {
properties.put(name, value);
}
public boolean containsProperty(String name) {
return properties.containsKey(name);
}
public void clearProperties() {
properties.clear();
}
public Map<String, String> getProperties() {
return Collections.unmodifiableMap(properties);
}
@ -382,6 +450,10 @@ public ClassLoaderConfig getClassLoaderConfig() {
return classLoaderConfig;
}
public void setClassLoaderConfig(ClassLoaderConfig classLoaderConfig) {
this.classLoaderConfig = classLoaderConfig;
}
public boolean isLimited() {
return limited;
}
@ -390,8 +462,16 @@ public List<CompatibilityFlags> getFlags() {
return flags;
}
public Path getProfileFilePath() {
return profileFilePath;
}
public void setProfileFilePath(Path profileFilePath) {
this.profileFilePath = profileFilePath;
}
public enum ClassLoaderConfig {
LAUNCHER, MODULE, SYSTEM_ARGS
AGENT, LAUNCHER, MODULE, SYSTEM_ARGS
}
public enum CompatibilityFlags {

View file

@ -1,144 +1,42 @@
package pro.gravit.launcher.base.profiles;
import pro.gravit.launcher.base.profiles.optional.OptionalFile;
import pro.gravit.utils.launch.LaunchOptions;
import java.util.*;
public class ClientProfileBuilder {
private String title;
private UUID uuid;
private List<String> update = new ArrayList<>();
private List<String> updateExclusions = new ArrayList<>();
private List<String> updateShared = new ArrayList<>();
private List<String> updateVerify = new ArrayList<>();
private Set<OptionalFile> updateOptional = new HashSet<>();
private List<String> jvmArgs = new ArrayList<>();
private List<String> classPath = new ArrayList<>();
private List<String> modulePath = new ArrayList<>();
private List<String> modules = new ArrayList<>();
private List<String> altClassPath = new ArrayList<>();
private List<String> clientArgs = new ArrayList<>();
private List<String> compatClasses = new ArrayList<>();
private Map<String, String> properties = new HashMap<>();
private List<ClientProfile.ServerProfile> servers = new ArrayList<>();
private ClientProfile.ClassLoaderConfig classLoaderConfig = ClientProfile.ClassLoaderConfig.LAUNCHER;
private List<ClientProfile.CompatibilityFlags> flags = new ArrayList<>();
private ClientProfile.Version version;
private String info;
private String dir;
private int sortIndex;
private String assetIndex;
private String dir;
private String assetDir;
private List<String> update;
private List<String> updateExclusions;
private List<String> updateVerify;
private Set<OptionalFile> updateOptional;
private List<String> jvmArgs;
private List<String> classPath;
private List<String> altClassPath;
private List<String> clientArgs;
private List<String> compatClasses;
private List<String> loadNatives;
private Map<String, String> properties;
private List<ClientProfile.ServerProfile> servers;
private ClientProfile.ClassLoaderConfig classLoaderConfig;
private List<ClientProfile.CompatibilityFlags> flags;
private int recommendJavaVersion;
private int minJavaVersion;
private int maxJavaVersion;
private ClientProfile.ProfileDefaultSettings settings;
private boolean limited;
private int recommendJavaVersion = 8;
private int minJavaVersion = 8;
private int maxJavaVersion = 999;
private ClientProfile.ProfileDefaultSettings settings = new ClientProfile.ProfileDefaultSettings();
private int sortIndex;
private UUID uuid;
private String title;
private String info;
private String mainClass;
private String mainModule;
private LaunchOptions.ModuleConf moduleConf;
public ClientProfileBuilder() {
this.update = new ArrayList<>();
this.updateExclusions = new ArrayList<>();
this.updateVerify = new ArrayList<>();
this.updateOptional = new HashSet<>();
this.jvmArgs = new ArrayList<>();
this.classPath = new ArrayList<>();
this.altClassPath = new ArrayList<>();
this.clientArgs = new ArrayList<>();
this.compatClasses = new ArrayList<>();
this.loadNatives = new ArrayList<>();
this.properties = new HashMap<>();
this.servers = new ArrayList<>();
this.flags = new ArrayList<>();
this.settings = new ClientProfile.ProfileDefaultSettings();
this.recommendJavaVersion = 21;
this.minJavaVersion = 17;
this.maxJavaVersion = 999;
this.classLoaderConfig = ClientProfile.ClassLoaderConfig.LAUNCHER;
}
public ClientProfileBuilder(ClientProfile profile) {
this.title = profile.getTitle();
this.uuid = profile.getUUID();
this.version = profile.getVersion();
this.info = profile.getInfo();
this.dir = profile.getDir();
this.sortIndex = profile.getSortIndex();
this.assetIndex = profile.getAssetIndex();
this.assetDir = profile.getAssetDir();
this.update = new ArrayList<>(profile.getUpdate());
this.updateExclusions = new ArrayList<>(profile.getUpdateExclusions());
this.updateVerify = new ArrayList<>(profile.getUpdateVerify());
this.updateOptional = new HashSet<>(profile.getOptional());
this.jvmArgs = new ArrayList<>(profile.getJvmArgs());
this.classPath = new ArrayList<>(profile.getClassPath());
this.altClassPath = new ArrayList<>(profile.getAlternativeClassPath());
this.clientArgs = new ArrayList<>(profile.getClientArgs());
this.compatClasses = new ArrayList<>(profile.getCompatClasses());
this.loadNatives = new ArrayList<>(profile.getLoadNatives());
this.properties = new HashMap<>(profile.getProperties());
this.servers = new ArrayList<>(profile.getServers());
this.classLoaderConfig = profile.getClassLoaderConfig();
this.flags = new ArrayList<>(profile.getFlags());
this.recommendJavaVersion = profile.getRecommendJavaVersion();
this.minJavaVersion = profile.getMinJavaVersion();
this.maxJavaVersion = profile.getMaxJavaVersion();
this.settings = profile.getSettings();
this.limited = profile.isLimited();
this.mainClass = profile.getMainClass();
this.mainModule = profile.getMainModule();
this.moduleConf = profile.getModuleConf();
}
public ClientProfileBuilder setTitle(String title) {
this.title = title;
return this;
}
public ClientProfileBuilder setUuid(UUID uuid) {
this.uuid = uuid;
return this;
}
public ClientProfileBuilder setVersion(ClientProfile.Version version) {
this.version = version;
return this;
}
public ClientProfileBuilder setInfo(String info) {
this.info = info;
return this;
}
public ClientProfileBuilder setDir(String dir) {
this.dir = dir;
return this;
}
public ClientProfileBuilder setSortIndex(int sortIndex) {
this.sortIndex = sortIndex;
return this;
}
public ClientProfileBuilder setAssetIndex(String assetIndex) {
this.assetIndex = assetIndex;
return this;
}
public ClientProfileBuilder setAssetDir(String assetDir) {
this.assetDir = assetDir;
return this;
}
public ClientProfileBuilder setUpdate(List<String> update) {
public void setUpdate(List<String> update) {
this.update = update;
return this;
}
public ClientProfileBuilder update(String value) {
this.update.add(value);
return this;
}
public ClientProfileBuilder setUpdateExclusions(List<String> updateExclusions) {
@ -146,70 +44,33 @@ public ClientProfileBuilder setUpdateExclusions(List<String> updateExclusions) {
return this;
}
public ClientProfileBuilder updateExclusions(String value) {
this.updateExclusions.add(value);
public ClientProfileBuilder setUpdateShared(List<String> updateShared) {
this.updateShared = updateShared;
return this;
}
public ClientProfileBuilder setUpdateVerify(List<String> updateVerify) {
public void setUpdateVerify(List<String> updateVerify) {
this.updateVerify = updateVerify;
return this;
}
public ClientProfileBuilder updateVerify(String value) {
this.updateVerify.add(value);
return this;
}
public ClientProfileBuilder setUpdateOptional(Set<OptionalFile> updateOptional) {
public void setUpdateOptional(Set<OptionalFile> updateOptional) {
this.updateOptional = updateOptional;
return this;
}
public ClientProfileBuilder updateOptional(OptionalFile value) {
this.updateOptional.add(value);
return this;
}
public ClientProfileBuilder setJvmArgs(List<String> jvmArgs) {
public void setJvmArgs(List<String> jvmArgs) {
this.jvmArgs = jvmArgs;
return this;
}
public ClientProfileBuilder jvmArg(String value) {
this.jvmArgs.add(value);
return this;
}
public ClientProfileBuilder setClassPath(List<String> classPath) {
public void setClassPath(List<String> classPath) {
this.classPath = classPath;
return this;
}
public ClientProfileBuilder classPath(String value) {
this.classPath.add(value);
return this;
}
public ClientProfileBuilder setAltClassPath(List<String> altClassPath) {
public void setAltClassPath(List<String> altClassPath) {
this.altClassPath = altClassPath;
return this;
}
public ClientProfileBuilder altClassPath(String value) {
this.altClassPath.add(value);
return this;
}
public ClientProfileBuilder setClientArgs(List<String> clientArgs) {
public void setClientArgs(List<String> clientArgs) {
this.clientArgs = clientArgs;
return this;
}
public ClientProfileBuilder clientArg(String value) {
this.clientArgs.add(value);
return this;
}
public ClientProfileBuilder setCompatClasses(List<String> compatClasses) {
@ -217,69 +78,55 @@ public ClientProfileBuilder setCompatClasses(List<String> compatClasses) {
return this;
}
public ClientProfileBuilder compatClass(String value) {
this.compatClasses.add(value);
return this;
}
public ClientProfileBuilder setLoadNatives(List<String> loadNatives) {
this.loadNatives = loadNatives;
return this;
}
public ClientProfileBuilder loadNatives(String value) {
this.loadNatives.add(value);
return this;
}
public ClientProfileBuilder setProperties(Map<String, String> properties) {
this.properties = properties;
return this;
}
public ClientProfileBuilder property(String name, String value) {
this.properties.put(name, value);
return this;
}
public ClientProfileBuilder setServers(List<ClientProfile.ServerProfile> servers) {
public void setServers(List<ClientProfile.ServerProfile> servers) {
this.servers = servers;
return this;
}
public ClientProfileBuilder server(ClientProfile.ServerProfile value) {
this.servers.add(value);
return this;
}
public ClientProfileBuilder setClassLoaderConfig(ClientProfile.ClassLoaderConfig classLoaderConfig) {
public void setClassLoaderConfig(ClientProfile.ClassLoaderConfig classLoaderConfig) {
this.classLoaderConfig = classLoaderConfig;
return this;
}
public ClientProfileBuilder setFlags(List<ClientProfile.CompatibilityFlags> flags) {
this.flags = flags;
return this;
public void setVersion(ClientProfile.Version version) {
this.version = version;
}
public ClientProfileBuilder flag(ClientProfile.CompatibilityFlags value) {
this.flags.add(value);
return this;
public void setAssetIndex(String assetIndex) {
this.assetIndex = assetIndex;
}
public ClientProfileBuilder setRecommendJavaVersion(int recommendJavaVersion) {
public void setDir(String dir) {
this.dir = dir;
}
public void setAssetDir(String assetDir) {
this.assetDir = assetDir;
}
public void setRecommendJavaVersion(int recommendJavaVersion) {
this.recommendJavaVersion = recommendJavaVersion;
}
public ClientProfileBuilder setModulePath(List<String> modulePath) {
this.modulePath = modulePath;
return this;
}
public ClientProfileBuilder setMinJavaVersion(int minJavaVersion) {
public ClientProfileBuilder setModules(List<String> modules) {
this.modules = modules;
return this;
}
public void setMinJavaVersion(int minJavaVersion) {
this.minJavaVersion = minJavaVersion;
return this;
}
public ClientProfileBuilder setMaxJavaVersion(int maxJavaVersion) {
public void setMaxJavaVersion(int maxJavaVersion) {
this.maxJavaVersion = maxJavaVersion;
return this;
}
public ClientProfileBuilder setSettings(ClientProfile.ProfileDefaultSettings settings) {
@ -287,27 +134,33 @@ public ClientProfileBuilder setSettings(ClientProfile.ProfileDefaultSettings set
return this;
}
public ClientProfileBuilder setLimited(boolean limited) {
this.limited = limited;
public ClientProfileBuilder setSortIndex(int sortIndex) {
this.sortIndex = sortIndex;
return this;
}
public ClientProfileBuilder setMainClass(String mainClass) {
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setTitle(String title) {
this.title = title;
}
public void setInfo(String info) {
this.info = info;
}
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
return this;
}
public ClientProfileBuilder setMainModule(String mainModule) {
this.mainModule = mainModule;
return this;
}
public ClientProfileBuilder setModuleConf(LaunchOptions.ModuleConf moduleConf) {
this.moduleConf = moduleConf;
public ClientProfileBuilder setFlags(List<ClientProfile.CompatibilityFlags> flags) {
this.flags = flags;
return this;
}
public ClientProfile createClientProfile() {
return new ClientProfile(title, uuid, version, info, dir, sortIndex, assetIndex, assetDir, update, updateExclusions, updateVerify, updateOptional, jvmArgs, classPath, altClassPath, clientArgs, compatClasses, loadNatives, properties, servers, classLoaderConfig, flags, recommendJavaVersion, minJavaVersion, maxJavaVersion, settings, limited, mainClass, mainModule, moduleConf);
return new ClientProfile(update, updateExclusions, updateShared, updateVerify, updateOptional, jvmArgs, classPath, modulePath, modules, altClassPath, clientArgs, compatClasses, properties, servers, classLoaderConfig, flags, version, assetIndex, dir, assetDir, recommendJavaVersion, minJavaVersion, maxJavaVersion, settings, sortIndex, uuid, title, info, mainClass);
}
}

View file

@ -18,5 +18,4 @@ private ClientProfileVersions() {
public static final ClientProfile.Version MINECRAFT_1_20 = ClientProfile.Version.of("1.20");
public static final ClientProfile.Version MINECRAFT_1_20_2 = ClientProfile.Version.of("1.20.2");
public static final ClientProfile.Version MINECRAFT_1_20_3 = ClientProfile.Version.of("1.20.3");
public static final ClientProfile.Version MINECRAFT_1_20_5 = ClientProfile.Version.of("1.20.5");
}

View file

@ -88,7 +88,7 @@ public Set<OptionalAction> getEnabledActions() {
public void fixDependencies() {
Set<OptionalFile> disabled = all.stream().filter(t -> !isEnabled(t)).collect(Collectors.toSet());
for (OptionalFile file : disabled) {
if (file.group != null && file.group.length > 0 && Arrays.stream(file.group).noneMatch(this::isEnabled)) {
if (file.group != null && Arrays.stream(file.group).noneMatch(this::isEnabled)) {
enable(file.group[0], false, null);
}
}

View file

@ -1,19 +1,17 @@
package pro.gravit.launcher.base.profiles.optional.actions;
import java.util.List;
public class OptionalActionClassPath extends OptionalAction {
public List<String> args;
public String[] args;
public boolean useAltClasspath = false;
public OptionalActionClassPath() {
}
public OptionalActionClassPath(List<String> args) {
public OptionalActionClassPath(String[] args) {
this.args = args;
}
public OptionalActionClassPath(List<String> args, boolean useAltClasspath) {
public OptionalActionClassPath(String[] args, boolean useAltClasspath) {
this.args = args;
this.useAltClasspath = useAltClasspath;
}

View file

@ -154,7 +154,7 @@ public static String getRefreshToken() {
public static void reconnect() throws Exception {
getRequestService().connect();
getRequestService().open();
restore();
}

View file

@ -3,6 +3,7 @@
import java.io.IOException;
public final class RequestException extends IOException {
private static final long serialVersionUID = 7558237657082664821L;
public RequestException(String message) {

View file

@ -6,7 +6,7 @@
public interface RequestService {
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException;
void connect() throws Exception;
void open() throws Exception;
void registerEventHandler(EventHandler handler);

View file

@ -9,7 +9,6 @@
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@ -34,7 +33,7 @@ public abstract class ClientJSONPoint implements WebSocket.Listener {
private final Object sendSyncObject = new Object();
private volatile StringBuilder builder = new StringBuilder();
public ClientJSONPoint(final String uri) {
public ClientJSONPoint(final String uri) throws SSLException {
this(URI.create(uri));
}
@ -64,7 +63,7 @@ public ClientJSONPoint(URI uri) {
}
}
public void connect() throws Exception {
public void open() throws Exception {
webSocket = webSocketBuilder.buildAsync(uri, this).get();
}
@ -98,17 +97,6 @@ public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String re
return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
}
@Override
public void onOpen(WebSocket webSocket) {
onOpen();
WebSocket.Listener.super.onOpen(webSocket);
}
@Override
public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
return WebSocket.Listener.super.onBinary(webSocket, data, last);
}
@Override
public void onError(WebSocket webSocket, Throwable error) {
LogHelper.error(error);
@ -126,7 +114,7 @@ public void send(String text) {
abstract void onOpen();
public void close() {
public void close() throws InterruptedException {
webSocket.abort();
}

View file

@ -33,7 +33,7 @@ public abstract class ClientWebSocketService extends ClientJSONPoint {
public OnCloseCallback onCloseCallback;
public ReconnectCallback reconnectCallback;
public ClientWebSocketService(String address) {
public ClientWebSocketService(String address) throws SSLException {
super(createURL(address));
this.gson = Launcher.gsonManager.gson;
this.onConnect = true;

View file

@ -45,7 +45,7 @@ public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> reques
}
@Override
public void connect() {
public void open() {
}

View file

@ -11,12 +11,9 @@
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@ -43,7 +40,11 @@ public static CompletableFuture<StdWebSocketService> initWebSockets(String addre
service.openAsync(() -> {
future.complete(service);
JVMHelper.RUNTIME.addShutdownHook(new Thread(() -> {
try {
service.close();
} catch (InterruptedException e) {
LogHelper.error(e);
}
}));
}, future::completeExceptionally);
return future;

View file

@ -26,7 +26,7 @@ public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> reques
}
@Override
public void connect() {
public void open() {
}

View file

@ -7,9 +7,9 @@
public class ClientVersionTest {
@Test
public void parseTest() {
Assertions.assertEquals("1.0.0", ClientProfile.Version.of("1.0.0").toCleanString());
Assertions.assertEquals("1.0.0.1", ClientProfile.Version.of("1.0.0-1").toCleanString());
Assertions.assertEquals("1.0.0", ClientProfile.Version.of("-----1.0.0").toCleanString());
Assertions.assertEquals(ClientProfile.Version.of("1.0.0").toCleanString(), "1.0.0");
Assertions.assertEquals(ClientProfile.Version.of("1.0.0-1").toCleanString(), "1.0.0.1");
Assertions.assertEquals(ClientProfile.Version.of("-----1.0.0").toCleanString(), "1.0.0");
}
@Test

View file

@ -49,12 +49,12 @@ public void dependenciesTest() {
moduleManager.loadModule(new Depend3Module());
moduleManager.loadModule(new MainModule());
moduleManager.initModules(null);
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("depend1").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("depend2").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("depend3").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("internal").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("virtual").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("main").getInitStatus());
Assertions.assertEquals(moduleManager.getModule("depend1").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("depend2").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("depend3").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("internal").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("virtual").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("main").getInitStatus(), LauncherModule.InitStatus.FINISH);
}
@Test
@ -62,7 +62,7 @@ public void cyclicTest() {
moduleManager.loadModule(new CyclicDependModule());
moduleManager.loadModule(new Cyclic2DependModule());
moduleManager.initModules(null);
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("cyclic1").getInitStatus());
Assertions.assertEquals(LauncherModule.InitStatus.FINISH, moduleManager.getModule("cyclic2").getInitStatus());
Assertions.assertEquals(moduleManager.getModule("cyclic1").getInitStatus(), LauncherModule.InitStatus.FINISH);
Assertions.assertEquals(moduleManager.getModule("cyclic2").getInitStatus(), LauncherModule.InitStatus.FINISH);
}
}

View file

@ -16,10 +16,10 @@ public Depend1Module() {
@Override
public void init(LauncherInitContext initContext) {
InternalModule module = modulesManager.getModule(InternalModule.class);
Assertions.assertEquals(InitStatus.FINISH, module.getInitStatus());
Assertions.assertEquals(module.getInitStatus(), InitStatus.FINISH);
Depend3Module module1 = modulesManager.getModule(Depend3Module.class);
Assertions.assertEquals(InitStatus.FINISH, module1.getInitStatus());
Assertions.assertEquals(module1.getInitStatus(), InitStatus.FINISH);
VirtualInterface virtualInterface = modulesManager.getModuleByInterface(VirtualInterface.class);
Assertions.assertEquals(InitStatus.FINISH, ((LauncherModule) virtualInterface).getInitStatus());
Assertions.assertEquals(((LauncherModule) virtualInterface).getInitStatus(), InitStatus.FINISH);
}
}

View file

@ -17,8 +17,8 @@ public MainModule() {
@Override
public void init(LauncherInitContext initContext) {
Depend1Module module = modulesManager.getModule(Depend1Module.class);
Assertions.assertEquals(InitStatus.FINISH, module.getInitStatus());
Assertions.assertEquals(module.getInitStatus(), InitStatus.FINISH);
Depend2Module module2 = modulesManager.getModule(Depend2Module.class);
Assertions.assertEquals(InitStatus.FINISH, module2.getInitStatus());
Assertions.assertEquals(module2.getInitStatus(), InitStatus.FINISH);
}
}

View file

@ -1,6 +1,7 @@
apply plugin: 'org.openjfx.javafxplugin'
String mainClassName = "pro.gravit.launcher.start.ClientLauncherWrapper"
String mainClassName = "pro.gravit.launcher.ClientLauncherWrapper"
String mainAgentName = "pro.gravit.launcher.LauncherAgent"
repositories {
maven {
@ -13,6 +14,7 @@
jar {
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName,
"Multi-Release": "true")
}

View file

@ -86,9 +86,11 @@ private static void realMain(String[] args) throws Throwable {
modulesManager.invokeEvent(new PreConfigPhase());
LogHelper.debug("Reading ClientLauncher params");
ClientParams params = readParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
ClientLauncherMethods.verifyNoAgent();
}
if(params.timestamp > System.currentTimeMillis() || params.timestamp + 30*1000 < System.currentTimeMillis() ) {
LogHelper.error("Timestamp failed: current %d | params %d | diff %d", System.currentTimeMillis(), params.timestamp, System.currentTimeMillis() - params.timestamp);
LogHelper.error("Timestamp failed. Exit");
ClientLauncherMethods.exitLauncher(-662);
return;
}
@ -118,13 +120,14 @@ private static void realMain(String[] args) throws Throwable {
LogHelper.debug("Verifying ClientLauncher sign and classpath");
Set<Path> ignoredPath = new HashSet<>();
List<Path> classpath = resolveClassPath(ignoredPath, clientDir, params.actions, params.profile)
.filter(x -> !profile.getModulePath().contains(clientDir.relativize(x).toString()))
.collect(Collectors.toCollection(ArrayList::new));
if(LogHelper.isDevEnabled()) {
for(var e : classpath) {
LogHelper.dev("Classpath entry %s", e);
}
}
List<URL> classpathURLs = classpath.stream().map(IOHelper::toURL).toList();
List<URL> classpathURLs = classpath.stream().map(IOHelper::toURL).collect(Collectors.toList());
// Start client with WatchService monitoring
RequestService service;
if (params.offlineMode) {
@ -158,7 +161,7 @@ private static void realMain(String[] args) throws Throwable {
System.load(Paths.get(params.nativesDir).resolve(ClientService.findLibrary(e)).toAbsolutePath().toString());
}
}
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER || classLoaderConfig == ClientProfile.ClassLoaderConfig.MODULE) {
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
if(JVMHelper.JVM_VERSION <= 11) {
launch = new LegacyLaunch();
} else {
@ -168,12 +171,20 @@ private static void realMain(String[] args) throws Throwable {
System.setProperty("java.class.path", classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, classLoaderControl, profile));
ClientService.baseURLs = classLoaderControl.getURLs();
} else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) {
launch = new BasicLaunch(LauncherAgent.inst);
classpathURLs.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
classLoaderControl = launch.init(classpath, params.nativesDir, options);
for (URL url : classpathURLs) {
LauncherAgent.addJVMClassPath(Paths.get(url.toURI()));
}
ClientService.instrumentation = LauncherAgent.inst;
modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, null, profile));
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
} else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) {
launch = new BasicLaunch();
classLoaderControl = launch.init(classpath, params.nativesDir, options);
ClientService.baseURLs = classpathURLs.toArray(new URL[0]);
} else {
throw new UnsupportedOperationException(String.format("Unknown classLoaderConfig %s", classLoaderConfig));
}
if(profile.hasFlag(ClientProfile.CompatibilityFlags.CLASS_CONTROL_API)) {
ClientService.classLoaderControl = classLoaderControl;
@ -242,11 +253,11 @@ public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher,
}
}
private static LinkedList<Path> resolveClassPathList(Set<Path> ignorePaths, Path clientDir, List<String> classPath) throws IOException {
private static LinkedList<Path> resolveClassPathList(Set<Path> ignorePaths, Path clientDir, String... classPath) throws IOException {
return resolveClassPathStream(ignorePaths, clientDir, classPath).collect(Collectors.toCollection(LinkedList::new));
}
private static Stream<Path> resolveClassPathStream(Set<Path> ignorePaths, Path clientDir, List<String> classPath) throws IOException {
private static Stream<Path> resolveClassPathStream(Set<Path> ignorePaths, Path clientDir, String... classPath) throws IOException {
Stream.Builder<Path> builder = Stream.builder();
for (String classPathEntry : classPath) {
Path path = clientDir.resolve(IOHelper.toPath(classPathEntry.replace(IOHelper.CROSS_SEPARATOR, IOHelper.PLATFORM_SEPARATOR)));
@ -290,7 +301,7 @@ private static void launch(ClientProfile profile, ClientParams params) throws Th
params.addClientLegacyArgs(args);
System.setProperty("minecraft.applet.TargetDirectory", params.clientDir);
}
args.addAll(profile.getClientArgs());
Collections.addAll(args, profile.getClientArgs());
for (OptionalAction action : params.actions) {
if (action instanceof OptionalActionClientArgs) {
args.addAll(((OptionalActionClientArgs) action).args);

View file

@ -0,0 +1,59 @@
package pro.gravit.launcher.client;
import pro.gravit.launcher.client.utils.NativeJVMHalt;
import pro.gravit.utils.helper.LogHelper;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Path;
import java.util.jar.JarFile;
public final class LauncherAgent {
public static Instrumentation inst;
private static boolean isAgentStarted = false;
public static void addJVMClassPath(String path) throws IOException {
LogHelper.debug("Launcher Agent addJVMClassPath");
inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
}
public static void addJVMClassPath(Path path) throws IOException {
LogHelper.debug("Launcher Agent addJVMClassPath");
inst.appendToSystemClassLoaderSearch(new JarFile(path.toFile()));
}
public static void premain(String agentArgument, Instrumentation instrumentation) {
System.out.println("Launcher Agent");
checkAgentStacktrace();
inst = instrumentation;
NativeJVMHalt.initFunc();
isAgentStarted = true;
}
public static void checkAgentStacktrace() {
RuntimeException ex = new SecurityException("Error check agent stacktrace");
boolean isFoundNative = false;
boolean foundPreMain = false;
for (StackTraceElement e : ex.getStackTrace()) {
if (e.isNativeMethod()) {
if (!isFoundNative) isFoundNative = true;
else throw ex;
}
if (e.getMethodName().equals("premain")) {
if (!foundPreMain) foundPreMain = true;
else throw ex;
}
}
if (!isFoundNative || !foundPreMain) throw ex;
}
public static boolean isStarted() {
return isAgentStarted;
}
public boolean isAgentStarted() {
return isAgentStarted;
}
}

View file

@ -6,6 +6,7 @@
compileOnly group: 'org.jline', name: 'jline', version: rootProject['verJline']
compileOnly group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
compileOnly group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
compileOnly group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: rootProject['verBcprov']
compileOnly group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
api group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']

View file

@ -282,7 +282,7 @@ public enum WalkAction {
@FunctionalInterface
public interface WalkCallback {
WalkAction walked(String path, String name, HashedEntry entry);
WalkAction walked(String path, String name, HashedEntry entry) throws IOException;
}
public static class FindRecursiveResult {

View file

@ -1,6 +1,7 @@
package pro.gravit.utils;
public class HookException extends RuntimeException {
private static final long serialVersionUID = -529141998961943161L;
public HookException(String message) {
super(message);

View file

@ -6,7 +6,7 @@ public final class Version implements Comparable<Version> {
public static final int MAJOR = 5;
public static final int MINOR = 6;
public static final int PATCH = 11;
public static final int PATCH = 1;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.STABLE;
public final int major;

View file

@ -1,6 +1,7 @@
package pro.gravit.utils.command;
public final class CommandException extends Exception {
private static final long serialVersionUID = -6588814993972117772L;
public CommandException(String message) {

View file

@ -10,18 +10,8 @@
import java.util.Map;
public abstract class CommandHandler implements Runnable {
protected final List<Category> categories;
protected final CommandCategory baseCategory;
public CommandHandler() {
this.categories = new ArrayList<>();
this.baseCategory = new BaseCommandCategory();
}
protected CommandHandler(List<Category> categories, CommandCategory baseCategory) {
this.categories = categories;
this.baseCategory = baseCategory;
}
private final List<Category> categories = new ArrayList<>();
private final CommandCategory baseCategory = new BaseCommandCategory();
public void eval(String line, boolean bell) {
LogHelper.info("Command '%s'", line);

View file

@ -5,8 +5,6 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class StdCommandHandler extends CommandHandler {
private final BufferedReader reader;
@ -16,31 +14,6 @@ public StdCommandHandler(boolean readCommands) {
reader = readCommands ? IOHelper.newReader(System.in) : null;
}
public StdCommandHandler(InputStream stream) {
super();
this.reader = IOHelper.newReader(stream);
}
public StdCommandHandler(BufferedReader reader) {
super();
this.reader = reader;
}
protected StdCommandHandler(List<Category> categories, CommandCategory baseCategory, boolean readCommands) {
super(categories, baseCategory);
this.reader = readCommands ? IOHelper.newReader(System.in) : null;
}
protected StdCommandHandler(List<Category> categories, CommandCategory baseCategory, InputStream stream) {
super(categories, baseCategory);
this.reader = IOHelper.newReader(stream);
}
protected StdCommandHandler(List<Category> categories, CommandCategory baseCategory, BufferedReader reader) {
super(categories, baseCategory);
this.reader = reader;
}
@Override
public void bell() {
}
@ -64,16 +37,4 @@ public void clear() throws IOException {
public String readLine() throws IOException {
return reader == null ? null : reader.readLine();
}
public StdCommandHandler ofHandler(CommandHandler commandHandler, boolean readCommands) {
return new StdCommandHandler(commandHandler.categories, commandHandler.baseCategory, readCommands);
}
public StdCommandHandler ofHandler(CommandHandler commandHandler, InputStream stream) {
return new StdCommandHandler(commandHandler.categories, commandHandler.baseCategory, stream);
}
public StdCommandHandler ofHandler(CommandHandler commandHandler, BufferedReader reader) {
return new StdCommandHandler(commandHandler.categories, commandHandler.baseCategory, reader);
}
}

View file

@ -221,40 +221,38 @@ public static ArgsParseResult parseJavaArgs(List<String> args) {
continue;
}
}
switch (arg) {
case "--module-path", "-p" -> {
if(arg.equals("--module-path") || arg.equals("-p")) {
prevArgType = PrevArgType.MODULE_PATH;
continue;
}
case "--classpath", "-cp" -> {
if(arg.equals("--classpath") || arg.equals("-cp")) {
prevArgType = PrevArgType.CLASSPATH;
continue;
}
case "--add-modules" -> {
if(arg.equals("--add-modules")) {
prevArgType = PrevArgType.ADD_MODULES;
continue;
}
case "--add-opens" -> {
if(arg.equals("--add-opens")) {
prevArgType = PrevArgType.ADD_OPENS;
continue;
}
case "--add-exports" -> {
if(arg.equals("--add-exports")) {
prevArgType = PrevArgType.ADD_EXPORTS;
continue;
}
case "--add-reads" -> {
if(arg.equals("--add-reads")) {
prevArgType = PrevArgType.ADD_READS;
continue;
}
case "--module", "-m" -> {
if(arg.equals("--module") || arg.equals("-m")) {
prevArgType = PrevArgType.MODULE;
continue;
}
case "-jar" -> {
if(arg.equals("-jar")) {
prevArgType = PrevArgType.JAR;
continue;
}
}
jvmArgs.add(arg);
}
return new ArgsParseResult(conf, classpath, jvmArgs, mainClass, mainModule, jarFile, args);

View file

@ -83,7 +83,7 @@ public static byte[] getClassBytes(Class<?> clazz, ClassLoader classLoader) thro
return IOHelper.read(classLoader.getResourceAsStream(getClassFile(clazz)));
}
public static InputStream getClassBytesStream(Class<?> clazz) {
public static InputStream getClassBytesStream(Class<?> clazz) throws IOException {
return getClassBytesStream(clazz, clazz.getClassLoader());
}

View file

@ -68,7 +68,7 @@ public void addTransformer(ClassTransformer transformer) {
}
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(transformer.filter(null, className)) {
return transformer.transform(null, className, protectionDomain, classfileBuffer);
}

View file

@ -54,10 +54,6 @@ private class LegacyClassLoader extends URLClassLoader {
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
private String nativePath;
static {
ClassLoader.registerAsParallelCapable();
}
private final List<String> packages = new ArrayList<>();
public LegacyClassLoader(URL[] urls) {
super(urls);
@ -123,9 +119,6 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
@Override
public String findLibrary(String name) {
if(nativePath == null) {
return null;
}
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION);
}

View file

@ -164,11 +164,6 @@ private class ModuleClassLoader extends URLClassLoader {
private String nativePath;
private final List<String> packages = new ArrayList<>();
static {
ClassLoader.registerAsParallelCapable();
}
public ModuleClassLoader(URL[] urls, ClassLoader parent) {
super("LAUNCHER", urls, parent);
packages.add("pro.gravit.launcher.");
@ -262,9 +257,6 @@ protected Class<?> findClass(String moduleName, String name) {
@Override
public String findLibrary(String name) {
if(nativePath == null) {
return null;
}
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION);
}

View file

@ -30,8 +30,8 @@ public void main() {
String json2 = gson.toJson(new MyTestClass2("BBBB"), TestInterface.class);
TestInterface test1 = gson.fromJson(json, TestInterface.class);
TestInterface test2 = gson.fromJson(json2, TestInterface.class);
Assertions.assertEquals("AAAA", test1.get());
Assertions.assertEquals("BBBB", test2.get());
Assertions.assertEquals(test1.get(), "AAAA");
Assertions.assertEquals(test2.get(), "BBBB");
}
public interface TestInterface {

View file

@ -32,7 +32,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ServerWrapper extends JsonConfigurable<ServerWrapper.Config> {
@ -122,7 +121,6 @@ public void run(String... args) throws Throwable {
}
LogHelper.debug("Read ServerWrapperConfig.json");
loadConfig();
config.applyEnv();
updateLauncherConfig();
Launcher.applyLauncherEnv(Objects.requireNonNullElse(config.env, LauncherConfig.LauncherEnvironment.STD));
StdWebSocketService service = StdWebSocketService.initWebSockets(config.address).get();
@ -306,38 +304,5 @@ public void apply() {
ConfigService.checkServerConfig.needProperties = checkServerNeedProperties;
}
}
public void applyEnv() {
this.authId = applyEnvOrDefault("SERVERWRAPPER_AUTH_ID", this.authId);
this.address = applyEnvOrDefault("SERVERWRAPPER_ADDRESS", this.address);
this.serverName = applyEnvOrDefault("SERVERWRAPPER_SERVER_NAME", this.serverName);
this.encodedServerEcPublicKey = applyEnvOrDefault("SERVERWRAPPER_EC_PUBLIC_KEY", Base64.getUrlDecoder()::decode, null);
this.encodedServerRsaPublicKey = applyEnvOrDefault("SERVERWRAPPER_RSA_PUBLIC_KEY", Base64.getUrlDecoder()::decode, null);
{
String token = System.getenv("SERVERWRAPPER_CHECK_SERVER_TOKEN");
if(token != null) {
if(extendedTokens == null) {
extendedTokens = new HashMap<>();
}
extendedTokens.put("checkServer", new Request.ExtendedToken(token, 0L));
}
}
}
private static String applyEnvOrDefault(String envName, String def) {
String value = System.getenv(envName);
if(value == null) {
return def;
}
return value;
}
private static<T> T applyEnvOrDefault(String envName, Function<String, T> mappingFunction, T def) {
String value = System.getenv(envName);
if(value == null) {
return def;
}
return mappingFunction.apply(value);
}
}
}

View file

@ -30,11 +30,8 @@ public ServerWrapperSetup() throws IOException {
public void run() throws Exception {
ServerWrapper wrapper = ServerWrapper.wrapper;
String jarName = System.getenv("SERVERWRAPPER_JAR_NAME");
if(jarName == null) {
System.out.println("Print server jar filename:");
jarName = commands.commandHandler.readLine();
}
String jarName = commands.commandHandler.readLine();
Path jarPath = Paths.get(jarName);
String mainClassName;
String agentClassName;
@ -59,18 +56,14 @@ public void run() throws Exception {
if (agentClassName != null) {
LogHelper.info("Found PremainClass %s", agentClassName);
}
if(wrapper.config.serverName == null || wrapper.config.serverName.isEmpty()) {
System.out.println("Print your server name:");
wrapper.config.serverName = commands.commandHandler.readLine();
}
wrapper.config.mainclass = mainClassName;
boolean altMode = false;
for (int i = 0; i < 10; ++i) {
if(!Request.isAvailable() || Request.getRequestService().isClosed()) {
if(wrapper.config.address == null || wrapper.config.address.isEmpty()) {
System.out.println("Print launchserver websocket host( ws://host:port/api ):");
wrapper.config.address = commands.commandHandler.readLine();
}
StdWebSocketService service;
try {
service = StdWebSocketService.initWebSockets(wrapper.config.address).get();
@ -80,11 +73,9 @@ public void run() throws Exception {
}
Request.setRequestService(service);
}
if(wrapper.config.extendedTokens == null || wrapper.config.extendedTokens.get("checkServer") == null) {
System.out.println("Print server token:");
String checkServerToken = commands.commandHandler.readLine();
wrapper.config.extendedTokens.put("checkServer", new Request.ExtendedToken(checkServerToken, 0));
}
wrapper.updateLauncherConfig();
try {
wrapper.restore();
@ -102,9 +93,7 @@ public void run() throws Exception {
}
if(wrapper.profile != null && wrapper.profile.getVersion().compareTo(ClientProfileVersions.MINECRAFT_1_18) >= 0) {
LogHelper.info("Switch to alternative start mode (1.18)");
if(!wrapper.config.classpath.contains(jarName)) {
wrapper.config.classpath.add(jarName);
}
wrapper.config.classLoaderConfig = ClientProfile.ClassLoaderConfig.LAUNCHER;
altMode = true;
}

View file

@ -1,11 +1,11 @@
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'maven-publish'
id 'signing'
id 'org.openjfx.javafxplugin' version '0.1.0' apply false
}
group = 'pro.gravit.launcher'
version = '5.6.11'
version = '5.6.1'
apply from: 'props.gradle'
@ -30,13 +30,6 @@
maven {
url "https://jitpack.io/"
}
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
}
jar {

Binary file not shown.

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