mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-23 17:11:39 +03:00
Merge branch 'release/5.1.4'
This commit is contained in:
commit
aed80e995a
185 changed files with 4209 additions and 4301 deletions
124
.github/workflows/push.yml
vendored
124
.github/workflows/push.yml
vendored
|
@ -9,74 +9,74 @@ jobs:
|
||||||
name: Launcher
|
name: Launcher
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.gradle/caches
|
path: ~/.gradle/caches
|
||||||
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
|
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 11
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
|
|
||||||
- name: Create artifacts
|
- name: Create artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts/modules
|
mkdir -p artifacts/modules
|
||||||
cd LaunchServer/build/libs/
|
cd LaunchServer/build/libs/
|
||||||
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
|
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
|
||||||
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
|
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
|
||||||
cd ../../../ServerWrapper/build/libs
|
cd ../../../ServerWrapper/build/libs
|
||||||
cp ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
|
cp ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
|
||||||
cd ../../../LauncherAuthlib/build/libs
|
cd ../../../LauncherAuthlib/build/libs
|
||||||
cp LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
|
cp LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
|
||||||
cd ../../../
|
cd ../../../
|
||||||
cp modules/*_module/build/libs/*.jar artifacts/modules
|
cp modules/*_module/build/libs/*.jar artifacts/modules
|
||||||
cp modules/*_swmodule/build/libs/*.jar artifacts/modules
|
cp modules/*_swmodule/build/libs/*.jar artifacts/modules
|
||||||
cp modules/*_lmodule/build/libs/*.jar artifacts/modules
|
cp modules/*_lmodule/build/libs/*.jar artifacts/modules
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: Launcher
|
name: Launcher
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
if: github.event_name == 'create'
|
if: github.event_name == 'create'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: GravitLauncher ${{ github.ref }}
|
release_name: GravitLauncher ${{ github.ref }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Pack release
|
- name: Pack release
|
||||||
if: github.event_name == 'create'
|
if: github.event_name == 'create'
|
||||||
run: |
|
run: |
|
||||||
cd artifacts/
|
cd artifacts/
|
||||||
zip -r -9 ../Release.zip *
|
zip -r -9 ../Release.zip *
|
||||||
|
|
||||||
- name: Upload release
|
- name: Upload release
|
||||||
if: github.event_name == 'create'
|
if: github.event_name == 'create'
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ./Release.zip
|
asset_path: ./Release.zip
|
||||||
asset_name: Release.zip
|
asset_name: Release.zip
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -107,6 +107,6 @@ buildnumber
|
||||||
*.directory
|
*.directory
|
||||||
cmd.bat
|
cmd.bat
|
||||||
cmd.sh
|
cmd.sh
|
||||||
|
project/target
|
||||||
## PVS Studio
|
## PVS Studio
|
||||||
.PVS-Studio/
|
.PVS-Studio/
|
||||||
project/target
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ task cleanjar(type: Jar, dependsOn: jar) {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
pack project(':LauncherAPI')
|
pack project(':LauncherAPI')
|
||||||
bundle group: 'org.fusesource.jansi', name:'jansi', version: rootProject['verJansi']
|
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', version: rootProject['verJline']
|
||||||
bundle group: 'org.jline', name: 'jline-reader', 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.jline', name: 'jline-terminal', version: rootProject['verJline']
|
||||||
|
@ -117,16 +117,18 @@ task hikari(type: Copy) {
|
||||||
task launch4j(type: Copy) {
|
task launch4j(type: Copy) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
into "$buildDir/libs/libraries/launch4j"
|
into "$buildDir/libs/libraries/launch4j"
|
||||||
from(configurations.launch4j.collect { it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it) })
|
from(configurations.launch4j.collect {
|
||||||
|
it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it)
|
||||||
|
})
|
||||||
includeEmptyDirs false
|
includeEmptyDirs false
|
||||||
eachFile { FileCopyDetails fcp ->
|
eachFile { FileCopyDetails fcp ->
|
||||||
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
|
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
|
||||||
fcp.relativePath.pathString.contains("workdir")) {
|
fcp.relativePath.pathString.contains("workdir")) {
|
||||||
def segments = fcp.relativePath.segments
|
def segments = fcp.relativePath.segments
|
||||||
def pathSegments = segments[1..-1] as String[]
|
def pathSegments = segments[1..-1] as String[]
|
||||||
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
|
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
|
||||||
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
|
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
|
||||||
fcp.mode = 0755
|
fcp.mode = 0755
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
import pro.gravit.launchserver.binary.*;
|
import pro.gravit.launchserver.binary.*;
|
||||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||||
|
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
|
||||||
import pro.gravit.launchserver.manangers.CertificateManager;
|
import pro.gravit.launchserver.manangers.CertificateManager;
|
||||||
import pro.gravit.launchserver.manangers.MirrorManager;
|
import pro.gravit.launchserver.manangers.MirrorManager;
|
||||||
import pro.gravit.launchserver.manangers.ReconfigurableManager;
|
import pro.gravit.launchserver.manangers.ReconfigurableManager;
|
||||||
|
@ -51,215 +52,49 @@
|
||||||
|
|
||||||
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
|
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
|
||||||
|
|
||||||
public enum ReloadType {
|
public static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
|
||||||
NO_AUTH,
|
|
||||||
NO_COMPONENTS,
|
|
||||||
FULL
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LaunchServerEnv {
|
|
||||||
TEST,
|
|
||||||
DEV,
|
|
||||||
DEBUG,
|
|
||||||
PRODUCTION
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface LaunchServerConfigManager {
|
|
||||||
LaunchServerConfig readConfig() throws IOException;
|
|
||||||
|
|
||||||
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
|
|
||||||
|
|
||||||
void writeConfig(LaunchServerConfig config) throws IOException;
|
|
||||||
|
|
||||||
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reload(ReloadType type) throws Exception {
|
|
||||||
config.close(type);
|
|
||||||
Map<String, AuthProviderPair> pairs = null;
|
|
||||||
if (type.equals(ReloadType.NO_AUTH)) {
|
|
||||||
pairs = config.auth;
|
|
||||||
}
|
|
||||||
LogHelper.info("Reading LaunchServer config file");
|
|
||||||
config = launchServerConfigManager.readConfig();
|
|
||||||
config.setLaunchServer(this);
|
|
||||||
if (type.equals(ReloadType.NO_AUTH)) {
|
|
||||||
config.auth = pairs;
|
|
||||||
}
|
|
||||||
config.verify();
|
|
||||||
config.init(type);
|
|
||||||
if (type.equals(ReloadType.FULL) && config.components != null) {
|
|
||||||
LogHelper.debug("PreInit components");
|
|
||||||
config.components.forEach((k, v) -> {
|
|
||||||
LogHelper.subDebug("PreInit component %s", k);
|
|
||||||
v.preInit(this);
|
|
||||||
});
|
|
||||||
LogHelper.debug("PreInit components successful");
|
|
||||||
LogHelper.debug("Init components");
|
|
||||||
config.components.forEach((k, v) -> {
|
|
||||||
LogHelper.subDebug("Init component %s", k);
|
|
||||||
v.init(this);
|
|
||||||
});
|
|
||||||
LogHelper.debug("Init components successful");
|
|
||||||
LogHelper.debug("PostInit components");
|
|
||||||
config.components.forEach((k, v) -> {
|
|
||||||
LogHelper.subDebug("PostInit component %s", k);
|
|
||||||
v.postInit(this);
|
|
||||||
});
|
|
||||||
LogHelper.debug("PostInit components successful");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Command> getCommands() {
|
|
||||||
Map<String, Command> commands = new HashMap<>();
|
|
||||||
SubCommand reload = new SubCommand() {
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) throws Exception {
|
|
||||||
if (args.length == 0) {
|
|
||||||
reload(ReloadType.FULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (args[0]) {
|
|
||||||
case "full":
|
|
||||||
reload(ReloadType.FULL);
|
|
||||||
break;
|
|
||||||
case "no_auth":
|
|
||||||
reload(ReloadType.NO_AUTH);
|
|
||||||
break;
|
|
||||||
case "no_components":
|
|
||||||
reload(ReloadType.NO_COMPONENTS);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
reload(ReloadType.FULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
commands.put("reload", reload);
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
private final Collection<ClientProfile> result;
|
|
||||||
|
|
||||||
private ProfilesFileVisitor(Collection<ClientProfile> result) {
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
LogHelper.info("Syncing '%s' 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.add(profile);
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant paths
|
|
||||||
|
|
||||||
public final Path dir;
|
public final Path dir;
|
||||||
|
|
||||||
public final LaunchServerEnv env;
|
public final LaunchServerEnv env;
|
||||||
|
|
||||||
public final Path launcherLibraries;
|
public final Path launcherLibraries;
|
||||||
|
|
||||||
public final Path launcherLibrariesCompile;
|
public final Path launcherLibrariesCompile;
|
||||||
|
|
||||||
public final Path caCertFile;
|
public final Path caCertFile;
|
||||||
|
|
||||||
|
// Constant paths
|
||||||
public final Path caKeyFile;
|
public final Path caKeyFile;
|
||||||
|
|
||||||
public final Path serverCertFile;
|
public final Path serverCertFile;
|
||||||
|
|
||||||
public final Path serverKeyFile;
|
public final Path serverKeyFile;
|
||||||
|
|
||||||
public final Path updatesDir;
|
public final Path updatesDir;
|
||||||
|
|
||||||
public final LaunchServerConfigManager launchServerConfigManager;
|
public final LaunchServerConfigManager launchServerConfigManager;
|
||||||
|
|
||||||
//public static LaunchServer server = null;
|
|
||||||
|
|
||||||
public final Path profilesDir;
|
public final Path profilesDir;
|
||||||
// Server config
|
|
||||||
|
|
||||||
public LaunchServerConfig config;
|
|
||||||
public final LaunchServerRuntimeConfig runtime;
|
public final LaunchServerRuntimeConfig runtime;
|
||||||
|
|
||||||
|
|
||||||
public final ECPublicKey publicKey;
|
public final ECPublicKey publicKey;
|
||||||
|
|
||||||
public final ECPrivateKey privateKey;
|
public final ECPrivateKey privateKey;
|
||||||
// Launcher binary
|
|
||||||
|
|
||||||
public final JARLauncherBinary launcherBinary;
|
public final JARLauncherBinary launcherBinary;
|
||||||
|
|
||||||
|
//public static LaunchServer server = null;
|
||||||
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
|
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
|
||||||
|
// Server config
|
||||||
public final LauncherBinary launcherEXEBinary;
|
public final LauncherBinary launcherEXEBinary;
|
||||||
// HWID ban + anti-brutforce
|
|
||||||
|
|
||||||
public final SessionManager sessionManager;
|
public final SessionManager sessionManager;
|
||||||
|
|
||||||
public final AuthHookManager authHookManager;
|
public final AuthHookManager authHookManager;
|
||||||
// Server
|
|
||||||
|
|
||||||
public final LaunchServerModulesManager modulesManager;
|
public final LaunchServerModulesManager modulesManager;
|
||||||
|
// Launcher binary
|
||||||
public final MirrorManager mirrorManager;
|
public final MirrorManager mirrorManager;
|
||||||
|
|
||||||
public final ReconfigurableManager reconfigurableManager;
|
public final ReconfigurableManager reconfigurableManager;
|
||||||
|
|
||||||
public final ConfigManager configManager;
|
public final ConfigManager configManager;
|
||||||
|
// HWID ban + anti-brutforce
|
||||||
public final CertificateManager certificateManager;
|
public final CertificateManager certificateManager;
|
||||||
|
|
||||||
public final ProguardConf proguardConf;
|
public final ProguardConf proguardConf;
|
||||||
|
// Server
|
||||||
|
|
||||||
public final CommandHandler commandHandler;
|
public final CommandHandler commandHandler;
|
||||||
|
|
||||||
public final NettyServerSocketHandler nettyServerSocketHandler;
|
public final NettyServerSocketHandler nettyServerSocketHandler;
|
||||||
|
public final Timer taskPool;
|
||||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
public final AtomicBoolean started = new AtomicBoolean(false);
|
||||||
|
public final LauncherModuleLoader launcherModuleLoader;
|
||||||
|
public LaunchServerConfig config;
|
||||||
|
public volatile Map<String, HashedDir> updatesDirMap;
|
||||||
// Updates and profiles
|
// Updates and profiles
|
||||||
private volatile List<ClientProfile> profilesList;
|
private volatile List<ClientProfile> profilesList;
|
||||||
public volatile Map<String, HashedDir> updatesDirMap;
|
|
||||||
|
|
||||||
public final Timer taskPool;
|
|
||||||
|
|
||||||
public static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
|
|
||||||
|
|
||||||
public static class LaunchServerDirectories {
|
|
||||||
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
|
|
||||||
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
|
|
||||||
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile";
|
|
||||||
public Path updatesDir;
|
|
||||||
public Path profilesDir;
|
|
||||||
public Path launcherLibrariesDir;
|
|
||||||
public Path launcherLibrariesCompileDir;
|
|
||||||
public Path dir;
|
|
||||||
public Path trustStore;
|
|
||||||
|
|
||||||
public void collect() {
|
|
||||||
if (updatesDir == null) updatesDir = dir.resolve(UPDATES_NAME);
|
|
||||||
if (profilesDir == null) profilesDir = dir.resolve(PROFILES_NAME);
|
|
||||||
if (trustStore == null) trustStore = dir.resolve(TRUSTSTORE_NAME);
|
|
||||||
if (launcherLibrariesDir == null) launcherLibrariesDir = dir.resolve(LAUNCHERLIBRARIES_NAME);
|
|
||||||
if (launcherLibrariesCompileDir == null) launcherLibrariesCompileDir = dir.resolve(LAUNCHERLIBRARIESCOMPILE_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, ECPublicKey publicKey, ECPrivateKey privateKey, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
|
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, ECPublicKey publicKey, ECPrivateKey privateKey, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
|
||||||
this.dir = directories.dir;
|
this.dir = directories.dir;
|
||||||
|
@ -365,7 +200,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
launcherBinary.init();
|
launcherBinary.init();
|
||||||
launcherEXEBinary.init();
|
launcherEXEBinary.init();
|
||||||
syncLauncherBinaries();
|
syncLauncherBinaries();
|
||||||
|
launcherModuleLoader = new LauncherModuleLoader(this);
|
||||||
// Sync updates dir
|
// Sync updates dir
|
||||||
if (!IOHelper.isDir(updatesDir))
|
if (!IOHelper.isDir(updatesDir))
|
||||||
Files.createDirectory(updatesDir);
|
Files.createDirectory(updatesDir);
|
||||||
|
@ -375,7 +210,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
if (!IOHelper.isDir(profilesDir))
|
if (!IOHelper.isDir(profilesDir))
|
||||||
Files.createDirectory(profilesDir);
|
Files.createDirectory(profilesDir);
|
||||||
syncProfilesDir();
|
syncProfilesDir();
|
||||||
|
launcherModuleLoader.init();
|
||||||
nettyServerSocketHandler = new NettyServerSocketHandler(this);
|
nettyServerSocketHandler = new NettyServerSocketHandler(this);
|
||||||
// post init modules
|
// post init modules
|
||||||
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
|
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
|
||||||
|
@ -389,6 +224,73 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reload(ReloadType type) throws Exception {
|
||||||
|
config.close(type);
|
||||||
|
Map<String, AuthProviderPair> pairs = null;
|
||||||
|
if (type.equals(ReloadType.NO_AUTH)) {
|
||||||
|
pairs = config.auth;
|
||||||
|
}
|
||||||
|
LogHelper.info("Reading LaunchServer config file");
|
||||||
|
config = launchServerConfigManager.readConfig();
|
||||||
|
config.setLaunchServer(this);
|
||||||
|
if (type.equals(ReloadType.NO_AUTH)) {
|
||||||
|
config.auth = pairs;
|
||||||
|
}
|
||||||
|
config.verify();
|
||||||
|
config.init(type);
|
||||||
|
if (type.equals(ReloadType.FULL) && config.components != null) {
|
||||||
|
LogHelper.debug("PreInit components");
|
||||||
|
config.components.forEach((k, v) -> {
|
||||||
|
LogHelper.subDebug("PreInit component %s", k);
|
||||||
|
v.preInit(this);
|
||||||
|
});
|
||||||
|
LogHelper.debug("PreInit components successful");
|
||||||
|
LogHelper.debug("Init components");
|
||||||
|
config.components.forEach((k, v) -> {
|
||||||
|
LogHelper.subDebug("Init component %s", k);
|
||||||
|
v.init(this);
|
||||||
|
});
|
||||||
|
LogHelper.debug("Init components successful");
|
||||||
|
LogHelper.debug("PostInit components");
|
||||||
|
config.components.forEach((k, v) -> {
|
||||||
|
LogHelper.subDebug("PostInit component %s", k);
|
||||||
|
v.postInit(this);
|
||||||
|
});
|
||||||
|
LogHelper.debug("PostInit components successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Command> getCommands() {
|
||||||
|
Map<String, Command> commands = new HashMap<>();
|
||||||
|
SubCommand reload = new SubCommand() {
|
||||||
|
@Override
|
||||||
|
public void invoke(String... args) throws Exception {
|
||||||
|
if (args.length == 0) {
|
||||||
|
reload(ReloadType.FULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (args[0]) {
|
||||||
|
case "full":
|
||||||
|
reload(ReloadType.FULL);
|
||||||
|
break;
|
||||||
|
case "no_auth":
|
||||||
|
reload(ReloadType.NO_AUTH);
|
||||||
|
break;
|
||||||
|
case "no_components":
|
||||||
|
reload(ReloadType.NO_COMPONENTS);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reload(ReloadType.FULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
commands.put("reload", reload);
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
private LauncherBinary binary() {
|
private LauncherBinary binary() {
|
||||||
if (launcherEXEBinaryClass != null) {
|
if (launcherEXEBinaryClass != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -406,7 +308,6 @@ private LauncherBinary binary() {
|
||||||
return new EXELauncherBinary(this);
|
return new EXELauncherBinary(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void buildLauncherBinaries() throws IOException {
|
public void buildLauncherBinaries() throws IOException {
|
||||||
launcherBinary.build();
|
launcherBinary.build();
|
||||||
launcherEXEBinary.build();
|
launcherEXEBinary.build();
|
||||||
|
@ -435,7 +336,6 @@ public HashedDir getUpdateDir(String name) {
|
||||||
return updatesDirMap.get(name);
|
return updatesDirMap.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Set<Entry<String, HashedDir>> getUpdateDirs() {
|
public Set<Entry<String, HashedDir>> getUpdateDirs() {
|
||||||
return updatesDirMap.entrySet();
|
return updatesDirMap.entrySet();
|
||||||
}
|
}
|
||||||
|
@ -467,7 +367,6 @@ public void run() {
|
||||||
modulesManager.invokeEvent(new LaunchServerFullInitEvent(this));
|
modulesManager.invokeEvent(new LaunchServerFullInitEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void syncLauncherBinaries() throws IOException {
|
public void syncLauncherBinaries() throws IOException {
|
||||||
LogHelper.info("Syncing launcher binaries");
|
LogHelper.info("Syncing launcher binaries");
|
||||||
|
|
||||||
|
@ -482,7 +381,6 @@ public void syncLauncherBinaries() throws IOException {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void syncProfilesDir() throws IOException {
|
public void syncProfilesDir() throws IOException {
|
||||||
LogHelper.info("Syncing profiles dir");
|
LogHelper.info("Syncing profiles dir");
|
||||||
List<ClientProfile> newProfies = new LinkedList<>();
|
List<ClientProfile> newProfies = new LinkedList<>();
|
||||||
|
@ -493,7 +391,6 @@ public void syncProfilesDir() throws IOException {
|
||||||
profilesList = Collections.unmodifiableList(newProfies);
|
profilesList = Collections.unmodifiableList(newProfies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
||||||
LogHelper.info("Syncing updates dir");
|
LogHelper.info("Syncing updates dir");
|
||||||
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
|
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
|
||||||
|
@ -565,4 +462,73 @@ public void fullyRestart() {
|
||||||
restart();
|
restart();
|
||||||
JVMHelper.RUNTIME.exit(0);
|
JVMHelper.RUNTIME.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public enum ReloadType {
|
||||||
|
NO_AUTH,
|
||||||
|
NO_COMPONENTS,
|
||||||
|
FULL
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LaunchServerEnv {
|
||||||
|
TEST,
|
||||||
|
DEV,
|
||||||
|
DEBUG,
|
||||||
|
PRODUCTION
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LaunchServerConfigManager {
|
||||||
|
LaunchServerConfig readConfig() throws IOException;
|
||||||
|
|
||||||
|
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
|
||||||
|
|
||||||
|
void writeConfig(LaunchServerConfig config) throws IOException;
|
||||||
|
|
||||||
|
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private final Collection<ClientProfile> result;
|
||||||
|
|
||||||
|
private ProfilesFileVisitor(Collection<ClientProfile> result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
LogHelper.info("Syncing '%s' 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.add(profile);
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LaunchServerDirectories {
|
||||||
|
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
|
||||||
|
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
|
||||||
|
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile";
|
||||||
|
public Path updatesDir;
|
||||||
|
public Path profilesDir;
|
||||||
|
public Path launcherLibrariesDir;
|
||||||
|
public Path launcherLibrariesCompileDir;
|
||||||
|
public Path dir;
|
||||||
|
public Path trustStore;
|
||||||
|
|
||||||
|
public void collect() {
|
||||||
|
if (updatesDir == null) updatesDir = dir.resolve(UPDATES_NAME);
|
||||||
|
if (profilesDir == null) profilesDir = dir.resolve(PROFILES_NAME);
|
||||||
|
if (trustStore == null) trustStore = dir.resolve(TRUSTSTORE_NAME);
|
||||||
|
if (launcherLibrariesDir == null) launcherLibrariesDir = dir.resolve(LAUNCHERLIBRARIES_NAME);
|
||||||
|
if (launcherLibrariesCompileDir == null)
|
||||||
|
launcherLibrariesCompileDir = dir.resolve(LAUNCHERLIBRARIESCOMPILE_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
|
import pro.gravit.launcher.LauncherTrustManager;
|
||||||
import pro.gravit.launcher.modules.events.PreConfigPhase;
|
import pro.gravit.launcher.modules.events.PreConfigPhase;
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
import pro.gravit.launchserver.auth.handler.AuthHandler;
|
import pro.gravit.launchserver.auth.handler.AuthHandler;
|
||||||
|
@ -23,7 +24,6 @@
|
||||||
import pro.gravit.utils.helper.JVMHelper;
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
import pro.gravit.launcher.LauncherTrustManager;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
@ -177,9 +177,9 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOExcept
|
||||||
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
|
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
|
||||||
directories.dir = dir;
|
directories.dir = dir;
|
||||||
if (inDocker) {
|
if (inDocker) {
|
||||||
Path parentLibraries = StarterAgent.libraries.toAbsolutePath().normalize().getParent();
|
Path parentLibraries = StarterAgent.libraries.toAbsolutePath().normalize().getParent();
|
||||||
directories.launcherLibrariesCompileDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIESCOMPILE_NAME);
|
directories.launcherLibrariesCompileDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIESCOMPILE_NAME);
|
||||||
directories.launcherLibrariesDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIES_NAME);
|
directories.launcherLibrariesDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIES_NAME);
|
||||||
}
|
}
|
||||||
LaunchServer server = new LaunchServerBuilder()
|
LaunchServer server = new LaunchServerBuilder()
|
||||||
.setDirectories(directories)
|
.setDirectories(directories)
|
||||||
|
|
|
@ -6,15 +6,30 @@
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
public final class StarterAgent {
|
public final class StarterAgent {
|
||||||
|
|
||||||
|
public static Instrumentation inst = null;
|
||||||
|
public static Path libraries = null;
|
||||||
|
private static boolean isStarted = false;
|
||||||
|
|
||||||
|
public static boolean isAgentStarted() {
|
||||||
|
return isStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void premain(String agentArgument, Instrumentation inst) {
|
||||||
|
StarterAgent.inst = inst;
|
||||||
|
libraries = Paths.get(Optional.ofNullable(agentArgument).map(e -> e.trim()).filter(e -> !e.isEmpty()).orElse("libraries"));
|
||||||
|
isStarted = true;
|
||||||
|
try {
|
||||||
|
Files.walkFileTree(libraries, Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new StarterVisitor());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class StarterVisitor extends SimpleFileVisitor<Path> {
|
private static final class StarterVisitor extends SimpleFileVisitor<Path> {
|
||||||
private static final Set<PosixFilePermission> DPERMS;
|
private static final Set<PosixFilePermission> DPERMS;
|
||||||
|
|
||||||
|
@ -49,23 +64,4 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
||||||
return super.visitFile(file, attrs);
|
return super.visitFile(file, attrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instrumentation inst = null;
|
|
||||||
public static Path libraries = null;
|
|
||||||
private static boolean isStarted = false;
|
|
||||||
|
|
||||||
public static boolean isAgentStarted() {
|
|
||||||
return isStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void premain(String agentArgument, Instrumentation inst) {
|
|
||||||
StarterAgent.inst = inst;
|
|
||||||
libraries = Paths.get(Optional.ofNullable(agentArgument).map(e -> e.trim()).filter(e -> !e.isEmpty()).orElse("libraries"));
|
|
||||||
isStarted = true;
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(libraries, Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new StarterVisitor());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,35 +18,20 @@
|
||||||
* чего угодно. Работает через поиск class-файлов в classpath.
|
* чего угодно. Работает через поиск class-файлов в classpath.
|
||||||
*/
|
*/
|
||||||
public class ClassMetadataReader implements Closeable {
|
public class ClassMetadataReader implements Closeable {
|
||||||
private static class CheckSuperClassVisitor extends ClassVisitor {
|
|
||||||
|
|
||||||
String superClassName;
|
|
||||||
|
|
||||||
public CheckSuperClassVisitor() {
|
|
||||||
super(Opcodes.ASM7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(int version, int access, String name, String signature, String superName,
|
|
||||||
String[] interfaces) {
|
|
||||||
superClassName = superName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<JarFile> cp;
|
private final List<JarFile> cp;
|
||||||
|
|
||||||
public ClassMetadataReader(List<JarFile> cp) {
|
public ClassMetadataReader(List<JarFile> cp) {
|
||||||
this.cp = cp;
|
this.cp = cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JarFile> getCp() {
|
|
||||||
return cp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClassMetadataReader() {
|
public ClassMetadataReader() {
|
||||||
this.cp = new ArrayList<>();
|
this.cp = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JarFile> getCp() {
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
|
public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
|
||||||
new ClassReader(classData).accept(visitor, 0);
|
new ClassReader(classData).accept(visitor, 0);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +48,6 @@ public void acceptVisitor(String className, ClassVisitor visitor, int flags) thr
|
||||||
acceptVisitor(getClassData(className), visitor, flags);
|
acceptVisitor(getClassData(className), visitor, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public byte[] getClassData(String className) throws IOException {
|
public byte[] getClassData(String className) throws IOException {
|
||||||
for (JarFile f : cp) {
|
for (JarFile f : cp) {
|
||||||
if (f.getEntry(className + ".class") != null) {
|
if (f.getEntry(className + ".class") != null) {
|
||||||
|
@ -111,4 +95,19 @@ public void close() {
|
||||||
cp.clear();
|
cp.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CheckSuperClassVisitor extends ClassVisitor {
|
||||||
|
|
||||||
|
String superClassName;
|
||||||
|
|
||||||
|
public CheckSuperClassVisitor() {
|
||||||
|
super(Opcodes.ASM7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName,
|
||||||
|
String[] interfaces) {
|
||||||
|
superClassName = superName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,245 +1,243 @@
|
||||||
package pro.gravit.launchserver.asm;
|
package pro.gravit.launchserver.asm;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.objectweb.asm.AnnotationVisitor;
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
import pro.gravit.launchserver.binary.BuildContext;
|
|
||||||
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
|
||||||
import pro.gravit.launcher.LauncherInject;
|
import pro.gravit.launcher.LauncherInject;
|
||||||
import pro.gravit.launcher.LauncherInjectionConstructor;
|
import pro.gravit.launcher.LauncherInjectionConstructor;
|
||||||
|
import pro.gravit.launchserver.binary.BuildContext;
|
||||||
|
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
|
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
|
||||||
private final Map<String, Object> values;
|
private static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class,
|
||||||
|
java.lang.Float.class, java.lang.Double.class, java.lang.String.class);
|
||||||
|
private static final String INJECTED_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
|
||||||
|
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.class);
|
||||||
|
private static final List<String> primitiveLDCDescriptors = Arrays.asList(Type.INT_TYPE.getDescriptor(), Type.DOUBLE_TYPE.getDescriptor(),
|
||||||
|
Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
|
||||||
|
private static final Map<Class<?>, Serializer<?>> serializers;
|
||||||
|
|
||||||
public InjectClassAcceptor(Map<String, Object> values) {
|
static {
|
||||||
this.values = values;
|
serializers = new HashMap<>();
|
||||||
}
|
serializers.put(List.class, new ListSerializer());
|
||||||
|
serializers.put(Map.class, new MapSerializer());
|
||||||
|
serializers.put(byte[].class, new ByteArraySerializer());
|
||||||
|
serializers.put(Short.class, serializerClass(Opcodes.I2S));
|
||||||
|
serializers.put(Byte.class, serializerClass(Opcodes.I2B));
|
||||||
|
serializers.put(Type.class, (Serializer<Type>) e -> { // ow.Type == java.lang.Class in LDC
|
||||||
|
InsnList ret = new InsnList();
|
||||||
|
ret.add(new LdcInsnNode(e));
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
serializers.put(Boolean.class, (Serializer<Boolean>) e -> {
|
||||||
|
InsnList ret = new InsnList();
|
||||||
|
ret.add(new InsnNode(e ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
serializers.put(Character.class, (Serializer<Character>) e -> {
|
||||||
|
InsnList ret = new InsnList();
|
||||||
|
ret.add(NodeUtils.push((int) e));
|
||||||
|
ret.add(new InsnNode(Opcodes.I2C));
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter);
|
||||||
|
}
|
||||||
|
|
||||||
private static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class,
|
private final Map<String, Object> values;
|
||||||
java.lang.Float.class, java.lang.Double.class, java.lang.String.class);
|
|
||||||
private static final String INJECTED_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
|
|
||||||
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.class);
|
|
||||||
private static final List<String> primitiveLDCDescriptors = Arrays.asList(Type.INT_TYPE.getDescriptor(), Type.DOUBLE_TYPE.getDescriptor(),
|
|
||||||
Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
|
|
||||||
|
|
||||||
private static void visit(ClassNode classNode, Map<String, Object> values) {
|
public InjectClassAcceptor(Map<String, Object> values) {
|
||||||
MethodNode clinitMethod = classNode.methods.stream().filter(methodNode -> "<clinit>".equals(methodNode.name))
|
this.values = values;
|
||||||
.findFirst().orElseGet(() -> {
|
}
|
||||||
MethodNode newClinitMethod = new MethodNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
|
|
||||||
"<clinit>", "()V", null, null);
|
|
||||||
newClinitMethod.instructions.add(new InsnNode(Opcodes.RETURN));
|
|
||||||
classNode.methods.add(newClinitMethod);
|
|
||||||
return newClinitMethod;
|
|
||||||
});
|
|
||||||
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
|
|
||||||
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
|
|
||||||
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
|
|
||||||
classNode.fields.forEach(field -> {
|
|
||||||
// Notice that fields that will be used with this algo should not have default
|
|
||||||
// value by = ...;
|
|
||||||
AnnotationNode valueAnnotation = field.invisibleAnnotations != null ? field.invisibleAnnotations.stream()
|
|
||||||
.filter(annotation -> INJECTED_FIELD_DESC.equals(annotation.desc)).findFirst()
|
|
||||||
.orElse(null) : null;
|
|
||||||
if (valueAnnotation == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
field.invisibleAnnotations.remove(valueAnnotation);
|
|
||||||
AtomicReference<String> valueName = new AtomicReference<String>(null);
|
|
||||||
valueAnnotation.accept(new AnnotationVisitor(Opcodes.ASM7) {
|
|
||||||
@Override
|
|
||||||
public void visit(final String name, final Object value) {
|
|
||||||
if ("value".equals(name)) {
|
|
||||||
if (value.getClass() != String.class)
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
String.format("Invalid annotation with value class %s", field.getClass().getName()));
|
|
||||||
valueName.set(value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (valueName.get() == null) {
|
|
||||||
throw new IllegalArgumentException("Annotation should always contains 'value' key");
|
|
||||||
}
|
|
||||||
if (!values.containsKey(valueName.get())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object value = values.get(valueName.get());
|
|
||||||
if ((field.access & Opcodes.ACC_STATIC) != 0) {
|
|
||||||
if (primitiveLDCDescriptors.contains(field.desc) && primitiveLDCClasses.contains(value.getClass())) {
|
|
||||||
field.value = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<FieldInsnNode> putStaticNodes = Arrays.stream(clinitMethod.instructions.toArray())
|
|
||||||
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
|
|
||||||
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
|
||||||
InsnList setter = serializeValue(value);
|
|
||||||
if (putStaticNodes.isEmpty()) {
|
|
||||||
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
|
|
||||||
Arrays.stream(clinitMethod.instructions.toArray()).filter(node -> node.getOpcode() == Opcodes.RETURN)
|
|
||||||
.forEach(node -> clinitMethod.instructions.insertBefore(node, setter));
|
|
||||||
} else {
|
|
||||||
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
|
|
||||||
for (FieldInsnNode fieldInsnNode : putStaticNodes) {
|
|
||||||
clinitMethod.instructions.insertBefore(fieldInsnNode, setter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (initMethod == null) {
|
|
||||||
throw new IllegalArgumentException(String.format("Not found init in target: %s", classNode.name));
|
|
||||||
}
|
|
||||||
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
|
|
||||||
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
|
|
||||||
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
|
||||||
InsnList setter = serializeValue(value);
|
|
||||||
if (putFieldNodes.isEmpty()) {
|
|
||||||
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
||||||
setter.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, field.name, field.desc));
|
|
||||||
Arrays.stream(initMethod.instructions.toArray())
|
|
||||||
.filter(node -> node.getOpcode() == Opcodes.RETURN)
|
|
||||||
.forEach(node -> initMethod.instructions.insertBefore(node, setter));
|
|
||||||
} else {
|
|
||||||
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
|
|
||||||
for (FieldInsnNode fieldInsnNode : putFieldNodes) {
|
|
||||||
initMethod.instructions.insertBefore(fieldInsnNode, setter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Map<Class<?>, Serializer<?>> serializers;
|
private static void visit(ClassNode classNode, Map<String, Object> values) {
|
||||||
|
MethodNode clinitMethod = classNode.methods.stream().filter(methodNode -> "<clinit>".equals(methodNode.name))
|
||||||
|
.findFirst().orElseGet(() -> {
|
||||||
|
MethodNode newClinitMethod = new MethodNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
|
||||||
|
"<clinit>", "()V", null, null);
|
||||||
|
newClinitMethod.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||||
|
classNode.methods.add(newClinitMethod);
|
||||||
|
return newClinitMethod;
|
||||||
|
});
|
||||||
|
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
|
||||||
|
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
|
||||||
|
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
|
||||||
|
classNode.fields.forEach(field -> {
|
||||||
|
// Notice that fields that will be used with this algo should not have default
|
||||||
|
// value by = ...;
|
||||||
|
AnnotationNode valueAnnotation = field.invisibleAnnotations != null ? field.invisibleAnnotations.stream()
|
||||||
|
.filter(annotation -> INJECTED_FIELD_DESC.equals(annotation.desc)).findFirst()
|
||||||
|
.orElse(null) : null;
|
||||||
|
if (valueAnnotation == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.invisibleAnnotations.remove(valueAnnotation);
|
||||||
|
AtomicReference<String> valueName = new AtomicReference<String>(null);
|
||||||
|
valueAnnotation.accept(new AnnotationVisitor(Opcodes.ASM7) {
|
||||||
|
@Override
|
||||||
|
public void visit(final String name, final Object value) {
|
||||||
|
if ("value".equals(name)) {
|
||||||
|
if (value.getClass() != String.class)
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Invalid annotation with value class %s", field.getClass().getName()));
|
||||||
|
valueName.set(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (valueName.get() == null) {
|
||||||
|
throw new IllegalArgumentException("Annotation should always contains 'value' key");
|
||||||
|
}
|
||||||
|
if (!values.containsKey(valueName.get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object value = values.get(valueName.get());
|
||||||
|
if ((field.access & Opcodes.ACC_STATIC) != 0) {
|
||||||
|
if (primitiveLDCDescriptors.contains(field.desc) && primitiveLDCClasses.contains(value.getClass())) {
|
||||||
|
field.value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<FieldInsnNode> putStaticNodes = Arrays.stream(clinitMethod.instructions.toArray())
|
||||||
|
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
|
||||||
|
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
||||||
|
InsnList setter = serializeValue(value);
|
||||||
|
if (putStaticNodes.isEmpty()) {
|
||||||
|
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
|
||||||
|
Arrays.stream(clinitMethod.instructions.toArray()).filter(node -> node.getOpcode() == Opcodes.RETURN)
|
||||||
|
.forEach(node -> clinitMethod.instructions.insertBefore(node, setter));
|
||||||
|
} else {
|
||||||
|
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
|
||||||
|
for (FieldInsnNode fieldInsnNode : putStaticNodes) {
|
||||||
|
clinitMethod.instructions.insertBefore(fieldInsnNode, setter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (initMethod == null) {
|
||||||
|
throw new IllegalArgumentException(String.format("Not found init in target: %s", classNode.name));
|
||||||
|
}
|
||||||
|
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
|
||||||
|
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
|
||||||
|
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
||||||
|
InsnList setter = serializeValue(value);
|
||||||
|
if (putFieldNodes.isEmpty()) {
|
||||||
|
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||||
|
setter.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, field.name, field.desc));
|
||||||
|
Arrays.stream(initMethod.instructions.toArray())
|
||||||
|
.filter(node -> node.getOpcode() == Opcodes.RETURN)
|
||||||
|
.forEach(node -> initMethod.instructions.insertBefore(node, setter));
|
||||||
|
} else {
|
||||||
|
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
|
||||||
|
for (FieldInsnNode fieldInsnNode : putFieldNodes) {
|
||||||
|
initMethod.instructions.insertBefore(fieldInsnNode, setter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
private static Serializer<?> serializerClass(int opcode) {
|
||||||
serializers = new HashMap<>();
|
return new Serializer<Number>() {
|
||||||
serializers.put(List.class, new ListSerializer());
|
@Override
|
||||||
serializers.put(Map.class, new MapSerializer());
|
public InsnList serialize(Number value) {
|
||||||
serializers.put(byte[].class, new ByteArraySerializer());
|
InsnList ret = new InsnList();
|
||||||
serializers.put(Short.class, serializerClass(Opcodes.I2S));
|
ret.add(NodeUtils.push(value.intValue()));
|
||||||
serializers.put(Byte.class, serializerClass(Opcodes.I2B));
|
ret.add(new InsnNode(opcode));
|
||||||
serializers.put(Type.class, (Serializer<Type>) e -> { // ow.Type == java.lang.Class in LDC
|
return ret;
|
||||||
InsnList ret = new InsnList();
|
}
|
||||||
ret.add(new LdcInsnNode(e));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Boolean.class, (Serializer<Boolean>) e -> {
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(new InsnNode(e ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Character.class, (Serializer<Character>) e -> {
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(NodeUtils.push((int) e));
|
|
||||||
ret.add(new InsnNode(Opcodes.I2C));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Serializer<?> serializerClass(int opcode) {
|
};
|
||||||
return new Serializer<Number>() {
|
}
|
||||||
@Override
|
|
||||||
public InsnList serialize(Number value) {
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(NodeUtils.push(((Number) value).intValue()));
|
|
||||||
ret.add(new InsnNode(opcode));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
@SuppressWarnings("unchecked")
|
||||||
}
|
private static InsnList serializeValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
InsnList insnList = new InsnList();
|
||||||
|
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
|
||||||
|
return insnList;
|
||||||
|
}
|
||||||
|
if (primitiveLDCClasses.contains(value.getClass())) {
|
||||||
|
InsnList insnList = new InsnList();
|
||||||
|
insnList.add(new LdcInsnNode(value));
|
||||||
|
return insnList;
|
||||||
|
}
|
||||||
|
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
|
||||||
|
if (serializerEntry.getKey().isInstance(value)) {
|
||||||
|
return ((Serializer) serializerEntry.getValue()).serialize(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
|
||||||
|
value.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@Override
|
||||||
private interface Serializer<T> {
|
public void transform(ClassNode classNode, String className, BuildContext context) {
|
||||||
InsnList serialize(T value);
|
visit(classNode, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@FunctionalInterface
|
||||||
private static InsnList serializeValue(Object value) {
|
private interface Serializer<T> {
|
||||||
if (value == null) {
|
InsnList serialize(T value);
|
||||||
InsnList insnList = new InsnList();
|
}
|
||||||
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
|
|
||||||
return insnList;
|
|
||||||
}
|
|
||||||
if (primitiveLDCClasses.contains(value.getClass())) {
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new LdcInsnNode(value));
|
|
||||||
return insnList;
|
|
||||||
}
|
|
||||||
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
|
|
||||||
if (serializerEntry.getKey().isInstance(value)) {
|
|
||||||
return ((Serializer) serializerEntry.getValue()).serialize(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
|
|
||||||
value.getClass()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ListSerializer implements Serializer<List> {
|
private static class ListSerializer implements Serializer<List> {
|
||||||
@Override
|
@Override
|
||||||
public InsnList serialize(List value) {
|
public InsnList serialize(List value) {
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
|
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
insnList.add(NodeUtils.push(value.size()));
|
insnList.add(NodeUtils.push(value.size()));
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
|
||||||
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
|
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
|
||||||
for (Object object : value) {
|
for (Object object : value) {
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
insnList.add(serializeValue(object));
|
insnList.add(serializeValue(object));
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
|
||||||
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
|
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
insnList.add(new InsnNode(Opcodes.POP));
|
||||||
}
|
}
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MapSerializer implements Serializer<Map> {
|
private static class MapSerializer implements Serializer<Map> {
|
||||||
@Override
|
@Override
|
||||||
public InsnList serialize(Map value) {
|
public InsnList serialize(Map value) {
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
|
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
|
||||||
Type.getMethodDescriptor(Type.VOID_TYPE), false));
|
Type.getMethodDescriptor(Type.VOID_TYPE), false));
|
||||||
for (Object entryObject : value.entrySet()) {
|
for (Object entryObject : value.entrySet()) {
|
||||||
Map.Entry entry = (Map.Entry) entryObject;
|
Map.Entry entry = (Map.Entry) entryObject;
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
insnList.add(serializeValue(entry.getKey()));
|
insnList.add(serializeValue(entry.getKey()));
|
||||||
insnList.add(serializeValue(entry.getValue()));
|
insnList.add(serializeValue(entry.getValue()));
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "put",
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "put",
|
||||||
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)),
|
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)),
|
||||||
true));
|
true));
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
insnList.add(new InsnNode(Opcodes.POP));
|
||||||
}
|
}
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ByteArraySerializer implements Serializer<byte[]> {
|
private static class ByteArraySerializer implements Serializer<byte[]> {
|
||||||
@Override
|
@Override
|
||||||
public InsnList serialize(byte[] value) {
|
public InsnList serialize(byte[] value) {
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
|
||||||
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
|
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
|
||||||
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
|
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
|
||||||
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
|
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
|
||||||
false));
|
false));
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void transform(ClassNode classNode, String className, BuildContext context) {
|
|
||||||
visit(classNode, values);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
public final class NodeUtils {
|
public final class NodeUtils {
|
||||||
|
|
||||||
|
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
|
||||||
|
|
||||||
private NodeUtils() {
|
private NodeUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +188,6 @@ public static InsnList getSafeStringInsnList(String string) {
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
|
|
||||||
|
|
||||||
public static String[] splitUtf8ToChunks(String text, int maxBytes) {
|
public static String[] splitUtf8ToChunks(String text, int maxBytes) {
|
||||||
List<String> parts = new ArrayList<>();
|
List<String> parts = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -238,9 +238,9 @@ else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InsnList makeValueEnumGetter(@SuppressWarnings("rawtypes") Enum u) {
|
public static InsnList makeValueEnumGetter(@SuppressWarnings("rawtypes") Enum u) {
|
||||||
InsnList ret = new InsnList();
|
InsnList ret = new InsnList();
|
||||||
Type e = Type.getType(u.getClass());
|
Type e = Type.getType(u.getClass());
|
||||||
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
|
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class AuthProviderPair {
|
public class AuthProviderPair {
|
||||||
|
public final boolean isDefault = true;
|
||||||
public AuthProvider provider;
|
public AuthProvider provider;
|
||||||
public AuthHandler handler;
|
public AuthHandler handler;
|
||||||
public TextureProvider textureProvider;
|
public TextureProvider textureProvider;
|
||||||
public Map<String, String> links;
|
public Map<String, String> links;
|
||||||
public transient String name;
|
public transient String name;
|
||||||
public String displayName;
|
public String displayName;
|
||||||
public final boolean isDefault = true;
|
|
||||||
|
|
||||||
public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvider textureProvider) {
|
public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvider textureProvider) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
|
@ -24,35 +24,33 @@ public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(LaunchServer srv, String name) {
|
public void init(LaunchServer srv, String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if(links != null) link(srv);
|
if (links != null) link(srv);
|
||||||
if(provider == null) throw new NullPointerException(String.format("Auth %s provider null", name));
|
if (provider == null) throw new NullPointerException(String.format("Auth %s provider null", name));
|
||||||
if(handler == null) throw new NullPointerException(String.format("Auth %s handler null", name));
|
if (handler == null) throw new NullPointerException(String.format("Auth %s handler null", name));
|
||||||
if(textureProvider == null) throw new NullPointerException(String.format("Auth %s textureProvider null", name));
|
if (textureProvider == null)
|
||||||
|
throw new NullPointerException(String.format("Auth %s textureProvider null", name));
|
||||||
provider.init(srv);
|
provider.init(srv);
|
||||||
handler.init(srv);
|
handler.init(srv);
|
||||||
}
|
}
|
||||||
public void link(LaunchServer srv)
|
|
||||||
{
|
public void link(LaunchServer srv) {
|
||||||
links.forEach((k,v) -> {
|
links.forEach((k, v) -> {
|
||||||
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
||||||
if(pair == null)
|
if (pair == null) {
|
||||||
{
|
|
||||||
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
|
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
|
||||||
}
|
}
|
||||||
if("provider".equals(k))
|
if ("provider".equals(k)) {
|
||||||
{
|
if (pair.provider == null)
|
||||||
if(pair.provider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.provider is null", name, v));
|
throw new NullPointerException(String.format("Auth %s link failed. %s.provider is null", name, v));
|
||||||
provider = pair.provider;
|
provider = pair.provider;
|
||||||
}
|
} else if ("handler".equals(k)) {
|
||||||
else if("handler".equals(k))
|
if (pair.handler == null)
|
||||||
{
|
throw new NullPointerException(String.format("Auth %s link failed. %s.handler is null", name, v));
|
||||||
if(pair.handler == null) throw new NullPointerException(String.format("Auth %s link failed. %s.handler is null", name, v));
|
|
||||||
handler = pair.handler;
|
handler = pair.handler;
|
||||||
}
|
} else if ("textureProvider".equals(k)) {
|
||||||
else if("textureProvider".equals(k))
|
if (pair.textureProvider == null)
|
||||||
{
|
throw new NullPointerException(String.format("Auth %s link failed. %s.textureProvider is null", name, v));
|
||||||
if(pair.textureProvider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.textureProvider is null", name, v));
|
|
||||||
textureProvider = pair.textureProvider;
|
textureProvider = pair.textureProvider;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,8 +21,8 @@ public final class PostgreSQLSourceConfig implements AutoCloseable {
|
||||||
private String poolName;
|
private String poolName;
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
private String address;
|
private String[] addresses;
|
||||||
private int port;
|
private int[] ports;
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private String database;
|
private String database;
|
||||||
|
@ -43,8 +43,8 @@ public synchronized Connection getConnection() throws SQLException {
|
||||||
PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
|
PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
|
||||||
|
|
||||||
// Set credentials
|
// Set credentials
|
||||||
postgresqlSource.setServerNames(new String[] {address}); //TODO support multinode PostgreSQL DB
|
postgresqlSource.setServerNames(addresses);
|
||||||
postgresqlSource.setPortNumbers(new int[] {port});
|
postgresqlSource.setPortNumbers(ports);
|
||||||
postgresqlSource.setUser(username);
|
postgresqlSource.setUser(username);
|
||||||
postgresqlSource.setPassword(password);
|
postgresqlSource.setPassword(password);
|
||||||
postgresqlSource.setDatabaseName(database);
|
postgresqlSource.setDatabaseName(database);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
public abstract class AuthHandler implements AutoCloseable {
|
public abstract class AuthHandler implements AutoCloseable {
|
||||||
public static final ProviderMap<AuthHandler> providers = new ProviderMap<>("AuthHandler");
|
public static final ProviderMap<AuthHandler> providers = new ProviderMap<>("AuthHandler");
|
||||||
private static boolean registredHandl = false;
|
private static boolean registredHandl = false;
|
||||||
|
protected transient LaunchServer srv;
|
||||||
|
|
||||||
public static UUID authError(String message) throws AuthException {
|
public static UUID authError(String message) throws AuthException {
|
||||||
throw new AuthException(message);
|
throw new AuthException(message);
|
||||||
|
@ -31,8 +31,6 @@ public static void registerHandlers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected transient LaunchServer srv;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the UUID associated with the account
|
* Returns the UUID associated with the account
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,26 +18,8 @@
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class CachedAuthHandler extends AuthHandler implements NeedGarbageCollection, Reconfigurable {
|
public abstract class CachedAuthHandler extends AuthHandler implements NeedGarbageCollection, Reconfigurable {
|
||||||
public static final class Entry {
|
private transient final Map<UUID, Entry> entryCache = new HashMap<>(1024);
|
||||||
|
private transient final Map<String, UUID> usernamesCache = new HashMap<>(1024);
|
||||||
public final UUID uuid;
|
|
||||||
private String username;
|
|
||||||
private String accessToken;
|
|
||||||
private String serverID;
|
|
||||||
|
|
||||||
|
|
||||||
public Entry(UUID uuid, String username, String accessToken, String serverID) {
|
|
||||||
this.uuid = Objects.requireNonNull(uuid, "uuid");
|
|
||||||
this.username = Objects.requireNonNull(username, "username");
|
|
||||||
this.accessToken = accessToken == null ? null : SecurityHelper.verifyToken(accessToken);
|
|
||||||
this.serverID = serverID == null ? null : VerifyHelper.verifyServerID(serverID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class EntryAndUsername {
|
|
||||||
public Map<UUID, CachedAuthHandler.Entry> entryCache;
|
|
||||||
public Map<String, UUID> usernameCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Command> getCommands() {
|
public Map<String, Command> getCommands() {
|
||||||
|
@ -91,10 +73,6 @@ public void invoke(String... args) throws Exception {
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private transient final Map<UUID, Entry> entryCache = new HashMap<>(1024);
|
|
||||||
private transient final Map<String, UUID> usernamesCache = new HashMap<>(1024);
|
|
||||||
|
|
||||||
|
|
||||||
protected void addEntry(Entry entry) {
|
protected void addEntry(Entry entry) {
|
||||||
Entry previous = entryCache.put(entry.uuid, entry);
|
Entry previous = entryCache.put(entry.uuid, entry);
|
||||||
if (previous != null)
|
if (previous != null)
|
||||||
|
@ -122,10 +100,8 @@ public synchronized UUID checkServer(String username, String serverID) throws IO
|
||||||
serverID.equals(entry.serverID) ? entry.uuid : null;
|
serverID.equals(entry.serverID) ? entry.uuid : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected abstract Entry fetchEntry(String username) throws IOException;
|
protected abstract Entry fetchEntry(String username) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
protected abstract Entry fetchEntry(UUID uuid) throws IOException;
|
protected abstract Entry fetchEntry(UUID uuid) throws IOException;
|
||||||
|
|
||||||
private Entry getEntry(String username) throws IOException {
|
private Entry getEntry(String username) throws IOException {
|
||||||
|
@ -187,7 +163,6 @@ public void loadUsernameCache(Map<String, UUID> map) {
|
||||||
|
|
||||||
protected abstract boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException;
|
protected abstract boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
protected abstract boolean updateServerID(UUID uuid, String serverID) throws IOException;
|
protected abstract boolean updateServerID(UUID uuid, String serverID) throws IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -201,4 +176,25 @@ public final synchronized String uuidToUsername(UUID uuid) throws IOException {
|
||||||
Entry entry = getEntry(uuid);
|
Entry entry = getEntry(uuid);
|
||||||
return entry == null ? null : entry.username;
|
return entry == null ? null : entry.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Entry {
|
||||||
|
|
||||||
|
public final UUID uuid;
|
||||||
|
private String username;
|
||||||
|
private String accessToken;
|
||||||
|
private String serverID;
|
||||||
|
|
||||||
|
|
||||||
|
public Entry(UUID uuid, String username, String accessToken, String serverID) {
|
||||||
|
this.uuid = Objects.requireNonNull(uuid, "uuid");
|
||||||
|
this.username = Objects.requireNonNull(username, "username");
|
||||||
|
this.accessToken = accessToken == null ? null : SecurityHelper.verifyToken(accessToken);
|
||||||
|
this.serverID = serverID == null ? null : VerifyHelper.verifyServerID(serverID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class EntryAndUsername {
|
||||||
|
public Map<UUID, CachedAuthHandler.Entry> entryCache;
|
||||||
|
public Map<String, UUID> usernameCache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pro.gravit.launchserver.auth.handler;
|
package pro.gravit.launchserver.auth.handler;
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
|
||||||
import pro.gravit.launcher.HTTPRequest;
|
import pro.gravit.launcher.HTTPRequest;
|
||||||
|
import pro.gravit.launcher.Launcher;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -12,6 +12,31 @@ public class JsonAuthHandler extends CachedAuthHandler {
|
||||||
public URL updateAuthUrl;
|
public URL updateAuthUrl;
|
||||||
public URL updateServerIdUrl;
|
public URL updateServerIdUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry fetchEntry(String username) throws IOException {
|
||||||
|
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUsername(username)), getUrl), Entry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry fetchEntry(UUID uuid) throws IOException {
|
||||||
|
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUUID(uuid)), getUrl), Entry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
|
||||||
|
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateAuthRequest(uuid, username, accessToken)), updateAuthUrl), SuccessResponse.class).success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
|
||||||
|
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateServerIDRequest(uuid, serverID)), updateServerIdUrl), SuccessResponse.class).success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class EntryRequestByUsername {
|
public static class EntryRequestByUsername {
|
||||||
public final String username;
|
public final String username;
|
||||||
|
|
||||||
|
@ -53,29 +78,4 @@ public UpdateServerIDRequest(UUID uuid, String serverID) {
|
||||||
public static class SuccessResponse {
|
public static class SuccessResponse {
|
||||||
public boolean success;
|
public boolean success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Entry fetchEntry(String username) throws IOException {
|
|
||||||
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUsername(username)), getUrl), Entry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Entry fetchEntry(UUID uuid) throws IOException {
|
|
||||||
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUUID(uuid)), getUrl), Entry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
|
|
||||||
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateAuthRequest(uuid, username, accessToken)), updateAuthUrl), SuccessResponse.class).success;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
|
|
||||||
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateServerIDRequest(uuid, serverID)), updateServerIdUrl), SuccessResponse.class).success;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,12 @@
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class RequestAuthHandler extends CachedAuthHandler {
|
public final class RequestAuthHandler extends CachedAuthHandler {
|
||||||
private String usernameFetch;
|
|
||||||
private String uuidFetch;
|
|
||||||
|
|
||||||
private String updateAuth;
|
|
||||||
private String updateServerID;
|
|
||||||
|
|
||||||
private final String splitSymbol = ":";
|
private final String splitSymbol = ":";
|
||||||
private final String goodResponse = "OK";
|
private final String goodResponse = "OK";
|
||||||
|
private String usernameFetch;
|
||||||
|
private String uuidFetch;
|
||||||
|
private String updateAuth;
|
||||||
|
private String updateServerID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(LaunchServer srv) {
|
public void init(LaunchServer srv) {
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
public class AdvancedProtectHandler extends ProtectHandler implements SecureProtectHandler {
|
public class AdvancedProtectHandler extends ProtectHandler implements SecureProtectHandler {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.protect;
|
package pro.gravit.launchserver.auth.protect;
|
||||||
|
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
public class NoProtectHandler extends ProtectHandler {
|
public class NoProtectHandler extends ProtectHandler {
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -14,6 +13,7 @@
|
||||||
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
|
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
|
||||||
public Map<String, List<String>> profileWhitelist = new HashMap<>();
|
public Map<String, List<String>> profileWhitelist = new HashMap<>();
|
||||||
public List<String> allowUpdates = new ArrayList<>();
|
public List<String> allowUpdates = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||||
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
||||||
|
@ -36,13 +36,12 @@ public boolean canChangeProfile(ClientProfile profile, Client client) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canGetUpdates(String updatesDirName, Client client) {
|
public boolean canGetUpdates(String updatesDirName, Client client) {
|
||||||
return client.profile != null && ( client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
|
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWhitelisted(String profileTitle, String username)
|
public boolean isWhitelisted(String profileTitle, String username) {
|
||||||
{
|
|
||||||
List<String> allowedUsername = profileWhitelist.get(profileTitle);
|
List<String> allowedUsername = profileWhitelist.get(profileTitle);
|
||||||
if(allowedUsername == null) return true;
|
if (allowedUsername == null) return true;
|
||||||
return allowedUsername.contains(username);
|
return allowedUsername.contains(username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,19 @@
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
public interface ProfilesProtectHandler {
|
public interface ProfilesProtectHandler {
|
||||||
default boolean canGetProfiles(Client client)
|
default boolean canGetProfiles(Client client) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default boolean canGetProfile(ClientProfile profile, Client client)
|
|
||||||
{
|
default boolean canGetProfile(ClientProfile profile, Client client) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default boolean canChangeProfile(ClientProfile profile, Client client)
|
|
||||||
{
|
default boolean canChangeProfile(ClientProfile profile, Client client) {
|
||||||
return client.isAuth;
|
return client.isAuth;
|
||||||
}
|
}
|
||||||
default boolean canGetUpdates(String updatesDirName, Client client)
|
|
||||||
{
|
default boolean canGetUpdates(String updatesDirName, Client client) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,21 +12,23 @@
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
public interface SecureProtectHandler {
|
public interface SecureProtectHandler {
|
||||||
default byte[] generateSecureLevelKey()
|
default byte[] generateSecureLevelKey() {
|
||||||
{
|
|
||||||
return SecurityHelper.randomBytes(128);
|
return SecurityHelper.randomBytes(128);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void verifySecureLevelKey(byte[] publicKey, byte[] data, byte[] signature) throws InvalidKeySpecException, SignatureException {
|
default void verifySecureLevelKey(byte[] publicKey, byte[] data, byte[] signature) throws InvalidKeySpecException, SignatureException {
|
||||||
if(publicKey == null || signature == null) throw new InvalidKeySpecException();
|
if (publicKey == null || signature == null) throw new InvalidKeySpecException();
|
||||||
ECPublicKey pubKey = SecurityHelper.toPublicECKey(publicKey);
|
ECPublicKey pubKey = SecurityHelper.toPublicECKey(publicKey);
|
||||||
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
|
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
|
||||||
sign.update(data);
|
sign.update(data);
|
||||||
sign.verify(signature);
|
sign.verify(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
|
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
|
||||||
|
|
||||||
boolean allowGetSecureLevelInfo(Client client);
|
boolean allowGetSecureLevelInfo(Client client);
|
||||||
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client)
|
|
||||||
{
|
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client) {
|
||||||
return new SecurityReportRequestEvent();
|
return new SecurityReportRequestEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,6 @@ public abstract class AuthProvider implements AutoCloseable {
|
||||||
private static boolean registredProv = false;
|
private static boolean registredProv = false;
|
||||||
protected transient LaunchServer srv = null;
|
protected transient LaunchServer srv = null;
|
||||||
|
|
||||||
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getFirstAuthType() {
|
|
||||||
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.PASSWORD;
|
|
||||||
}
|
|
||||||
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getSecondAuthType() {
|
|
||||||
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AuthProviderResult authError(String message) throws AuthException {
|
public static AuthProviderResult authError(String message) throws AuthException {
|
||||||
throw new AuthException(message);
|
throw new AuthException(message);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +31,13 @@ public static void registerProviders() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getFirstAuthType() {
|
||||||
|
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.PASSWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getSecondAuthType() {
|
||||||
|
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the username and password
|
* Verifies the username and password
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
import pro.gravit.launcher.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.HTTPRequest;
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
import pro.gravit.launcher.HTTPRequest;
|
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -17,33 +17,6 @@ public final class JsonAuthProvider extends AuthProvider {
|
||||||
private URL url;
|
private URL url;
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
public static class authResult {
|
|
||||||
String username;
|
|
||||||
String error;
|
|
||||||
long permissions;
|
|
||||||
long flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class authRequest {
|
|
||||||
public authRequest(String username, String password, String ip) {
|
|
||||||
this.username = username;
|
|
||||||
this.password = password;
|
|
||||||
this.ip = ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public authRequest(String username, String password, String ip, String apiKey) {
|
|
||||||
this.username = username;
|
|
||||||
this.password = password;
|
|
||||||
this.ip = ip;
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String username;
|
|
||||||
final String password;
|
|
||||||
final String ip;
|
|
||||||
String apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
|
||||||
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||||
|
@ -66,4 +39,31 @@ else if (result.error != null)
|
||||||
public void close() {
|
public void close() {
|
||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class authResult {
|
||||||
|
String username;
|
||||||
|
String error;
|
||||||
|
long permissions;
|
||||||
|
long flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class authRequest {
|
||||||
|
final String username;
|
||||||
|
final String password;
|
||||||
|
final String ip;
|
||||||
|
String apiKey;
|
||||||
|
|
||||||
|
public authRequest(String username, String password, String ip) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.ip = ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public authRequest(String username, String password, String ip, String apiKey) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.ip = ip;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class RejectAuthProvider extends AuthProvider implements Reconfigurable {
|
public final class RejectAuthProvider extends AuthProvider implements Reconfigurable {
|
||||||
|
private String message;
|
||||||
|
private ArrayList<String> whitelist = new ArrayList<>();
|
||||||
|
|
||||||
public RejectAuthProvider() {
|
public RejectAuthProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +24,6 @@ public RejectAuthProvider(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String message;
|
|
||||||
private ArrayList<String> whitelist = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws AuthException {
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws AuthException {
|
||||||
if (whitelist != null) {
|
if (whitelist != null) {
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class RequestTextureProvider extends TextureProvider {
|
public final class RequestTextureProvider extends TextureProvider {
|
||||||
|
// Instance
|
||||||
|
private String skinURL;
|
||||||
|
private String cloakURL;
|
||||||
|
|
||||||
public RequestTextureProvider() {
|
public RequestTextureProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,11 +41,6 @@ private static String getTextureURL(String url, UUID uuid, String username, Stri
|
||||||
"client", IOHelper.urlEncode(client == null ? "unknown" : client));
|
"client", IOHelper.urlEncode(client == null ? "unknown" : client));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance
|
|
||||||
private String skinURL;
|
|
||||||
|
|
||||||
private String cloakURL;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
|
@ -55,7 +55,7 @@ public void replacePre(Predicate<LauncherBuildTask> pred, LauncherBuildTask task
|
||||||
}
|
}
|
||||||
|
|
||||||
public void replace(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
|
public void replace(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
|
||||||
replaceCounted( 0, pred, taskRep);
|
replaceCounted(0, pred, taskRep);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void replaceAfter(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
|
public void replaceAfter(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
|
||||||
|
|
|
@ -37,6 +37,115 @@ public class BuildContext {
|
||||||
public final HashSet<String> fileList;
|
public final HashSet<String> fileList;
|
||||||
public final HashSet<String> clientModules;
|
public final HashSet<String> clientModules;
|
||||||
|
|
||||||
|
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task) {
|
||||||
|
this.output = output;
|
||||||
|
this.readerClassPath = readerClassPath;
|
||||||
|
this.task = task;
|
||||||
|
fileList = new HashSet<>(1024);
|
||||||
|
clientModules = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushFile(String filename, InputStream inputStream) throws IOException {
|
||||||
|
ZipEntry zip = IOHelper.newZipEntry(filename);
|
||||||
|
output.putNextEntry(zip);
|
||||||
|
IOHelper.transfer(inputStream, output);
|
||||||
|
output.closeEntry();
|
||||||
|
fileList.add(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushFile(String filename, StreamObject object) throws IOException {
|
||||||
|
ZipEntry zip = IOHelper.newZipEntry(filename);
|
||||||
|
output.putNextEntry(zip);
|
||||||
|
object.write(new HOutput(output));
|
||||||
|
output.closeEntry();
|
||||||
|
fileList.add(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushFile(String filename, Object object, Type type) throws IOException {
|
||||||
|
String bytes = Launcher.gsonManager.gson.toJson(object, type);
|
||||||
|
pushBytes(filename, bytes.getBytes(UNICODE_CHARSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushDir(Path dir, String targetDir, Map<String, byte[]> hashMap, boolean hidden) throws IOException {
|
||||||
|
IOHelper.walk(dir, new RuntimeDirVisitor(output, hashMap, dir, targetDir), hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushBytes(String filename, byte[] bytes) throws IOException {
|
||||||
|
ZipEntry zip = IOHelper.newZipEntry(filename);
|
||||||
|
output.putNextEntry(zip);
|
||||||
|
output.write(bytes);
|
||||||
|
output.closeEntry();
|
||||||
|
fileList.add(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void pushJarFile(ZipInputStream input) throws IOException {
|
||||||
|
ZipEntry e = input.getNextEntry();
|
||||||
|
while (e != null) {
|
||||||
|
if (fileList.contains(e.getName())) {
|
||||||
|
e = input.getNextEntry();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output.putNextEntry(IOHelper.newZipEntry(e));
|
||||||
|
IOHelper.transfer(input, output);
|
||||||
|
fileList.add(e.getName());
|
||||||
|
e = input.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void pushJarFile(ZipInputStream input, Set<String> blacklist) throws IOException {
|
||||||
|
ZipEntry e = input.getNextEntry();
|
||||||
|
while (e != null) {
|
||||||
|
if (fileList.contains(e.getName()) || blacklist.contains(e.getName())) {
|
||||||
|
e = input.getNextEntry();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output.putNextEntry(IOHelper.newZipEntry(e));
|
||||||
|
IOHelper.transfer(input, output);
|
||||||
|
fileList.add(e.getName());
|
||||||
|
e = input.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
|
||||||
|
pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
|
||||||
|
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
|
||||||
|
ZipEntry e = input.getNextEntry();
|
||||||
|
while (e != null) {
|
||||||
|
String filename = e.getName();
|
||||||
|
if (e.isDirectory() || fileList.contains(filename) || filter.test(e)) {
|
||||||
|
e = input.getNextEntry();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
output.putNextEntry(IOHelper.newZipEntry(e));
|
||||||
|
} catch (ZipException ex) {
|
||||||
|
LogHelper.warning("Write %s failed: %s", filename, ex.getMessage() == null ? "null" : ex.getMessage());
|
||||||
|
e = input.getNextEntry();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (filename.endsWith(".class")) {
|
||||||
|
String classname = filename.replace('/', '.').substring(0,
|
||||||
|
filename.length() - ".class".length());
|
||||||
|
if (!needTransform.test(classname)) {
|
||||||
|
IOHelper.transfer(input, output);
|
||||||
|
} else {
|
||||||
|
byte[] bytes = IOHelper.read(input);
|
||||||
|
bytes = task.transformClass(bytes, classname, this);
|
||||||
|
output.write(bytes);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
IOHelper.transfer(input, output);
|
||||||
|
fileList.add(filename);
|
||||||
|
e = input.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
|
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
|
||||||
private final ZipOutputStream output;
|
private final ZipOutputStream output;
|
||||||
private final Map<String, byte[]> hashs;
|
private final Map<String, byte[]> hashs;
|
||||||
|
@ -60,7 +169,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
String fileName = IOHelper.toString(sourceDir.relativize(file));
|
String fileName = IOHelper.toString(sourceDir.relativize(file));
|
||||||
if(hashs != null)
|
if (hashs != null)
|
||||||
hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file));
|
hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file));
|
||||||
|
|
||||||
// Create zip entry and transfer contents
|
// Create zip entry and transfer contents
|
||||||
|
@ -72,122 +181,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
||||||
}
|
}
|
||||||
|
|
||||||
private ZipEntry newEntry(String fileName) {
|
private ZipEntry newEntry(String fileName) {
|
||||||
return newZipEntry( targetDir + IOHelper.CROSS_SEPARATOR + fileName);
|
return newZipEntry(targetDir + IOHelper.CROSS_SEPARATOR + fileName);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task) {
|
|
||||||
this.output = output;
|
|
||||||
this.readerClassPath = readerClassPath;
|
|
||||||
this.task = task;
|
|
||||||
fileList = new HashSet<>(1024);
|
|
||||||
clientModules = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushFile(String filename, InputStream inputStream) throws IOException {
|
|
||||||
ZipEntry zip = IOHelper.newZipEntry(filename);
|
|
||||||
output.putNextEntry(zip);
|
|
||||||
IOHelper.transfer(inputStream, output);
|
|
||||||
output.closeEntry();
|
|
||||||
fileList.add(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushFile(String filename, StreamObject object) throws IOException
|
|
||||||
{
|
|
||||||
ZipEntry zip = IOHelper.newZipEntry(filename);
|
|
||||||
output.putNextEntry(zip);
|
|
||||||
object.write(new HOutput(output));
|
|
||||||
output.closeEntry();
|
|
||||||
fileList.add(filename);
|
|
||||||
}
|
|
||||||
public void pushFile(String filename, Object object, Type type) throws IOException
|
|
||||||
{
|
|
||||||
String bytes = Launcher.gsonManager.gson.toJson(object, type);
|
|
||||||
pushBytes(filename, bytes.getBytes(UNICODE_CHARSET));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushDir(Path dir, String targetDir, Map<String, byte[]> hashMap, boolean hidden) throws IOException
|
|
||||||
{
|
|
||||||
IOHelper.walk(dir, new RuntimeDirVisitor(output, hashMap, dir, targetDir), hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushBytes(String filename, byte[] bytes) throws IOException {
|
|
||||||
ZipEntry zip = IOHelper.newZipEntry(filename);
|
|
||||||
output.putNextEntry(zip);
|
|
||||||
output.write(bytes);
|
|
||||||
output.closeEntry();
|
|
||||||
fileList.add(filename);
|
|
||||||
}
|
|
||||||
@Deprecated
|
|
||||||
public void pushJarFile(ZipInputStream input) throws IOException {
|
|
||||||
ZipEntry e = input.getNextEntry();
|
|
||||||
while (e != null) {
|
|
||||||
if (fileList.contains(e.getName())) {
|
|
||||||
e = input.getNextEntry();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
output.putNextEntry(IOHelper.newZipEntry(e));
|
|
||||||
IOHelper.transfer(input, output);
|
|
||||||
fileList.add(e.getName());
|
|
||||||
e = input.getNextEntry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Deprecated
|
|
||||||
public void pushJarFile(ZipInputStream input, Set<String> blacklist) throws IOException {
|
|
||||||
ZipEntry e = input.getNextEntry();
|
|
||||||
while (e != null) {
|
|
||||||
if (fileList.contains(e.getName()) || blacklist.contains(e.getName())) {
|
|
||||||
e = input.getNextEntry();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
output.putNextEntry(IOHelper.newZipEntry(e));
|
|
||||||
IOHelper.transfer(input, output);
|
|
||||||
fileList.add(e.getName());
|
|
||||||
e = input.getNextEntry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException
|
|
||||||
{
|
|
||||||
pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
|
|
||||||
}
|
|
||||||
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException
|
|
||||||
{
|
|
||||||
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
|
|
||||||
ZipEntry e = input.getNextEntry();
|
|
||||||
while(e != null)
|
|
||||||
{
|
|
||||||
String filename = e.getName();
|
|
||||||
if(e.isDirectory() || fileList.contains(filename) || filter.test(e))
|
|
||||||
{
|
|
||||||
e = input.getNextEntry();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
output.putNextEntry(IOHelper.newZipEntry(e));
|
|
||||||
} catch (ZipException ex) {
|
|
||||||
LogHelper.warning("Write %s failed: %s", filename, ex.getMessage() == null ? "null" : ex.getMessage());
|
|
||||||
e = input.getNextEntry();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (filename.endsWith(".class")) {
|
|
||||||
String classname = filename.replace('/', '.').substring(0,
|
|
||||||
filename.length() - ".class".length());
|
|
||||||
if(!needTransform.test(classname))
|
|
||||||
{
|
|
||||||
IOHelper.transfer(input, output);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byte[] bytes = IOHelper.read(input);
|
|
||||||
bytes = task.transformClass(bytes, classname, this);
|
|
||||||
output.write(bytes);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
IOHelper.transfer(input, output);
|
|
||||||
fileList.add(filename);
|
|
||||||
e = input.getNextEntry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
|
||||||
@Override
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
tasks.add(new PrepareBuildTask(server));
|
tasks.add(new PrepareBuildTask(server));
|
||||||
if(!server.config.sign.enabled) tasks.add(new CertificateAutogenTask(server));
|
if (!server.config.sign.enabled) tasks.add(new CertificateAutogenTask(server));
|
||||||
tasks.add(new MainBuildTask(server));
|
tasks.add(new MainBuildTask(server));
|
||||||
if (server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
|
if (server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
|
||||||
tasks.add(new ProGuardBuildTask(server));
|
tasks.add(new ProGuardBuildTask(server));
|
||||||
|
|
|
@ -19,17 +19,18 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
|
||||||
syncBinaryFile = binaryFile;
|
syncBinaryFile = binaryFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build() throws IOException
|
public static Path resolve(LaunchServer server, String ext) {
|
||||||
{
|
return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
|
||||||
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void build() throws IOException {
|
||||||
|
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
|
||||||
|
}
|
||||||
|
|
||||||
public final boolean exists() {
|
public final boolean exists() {
|
||||||
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
|
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final byte[] getDigest() {
|
public final byte[] getDigest() {
|
||||||
return digest;
|
return digest;
|
||||||
}
|
}
|
||||||
|
@ -48,8 +49,4 @@ public final boolean sync() throws IOException {
|
||||||
|
|
||||||
return exists;
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path resolve(LaunchServer server, String ext) {
|
|
||||||
return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,27 +14,16 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ProguardConf {
|
public class ProguardConf {
|
||||||
|
public static final String[] JAVA9_OPTS = new String[]{
|
||||||
|
"-libraryjars '<java.home>/jmods/'"
|
||||||
|
};
|
||||||
|
public static final String[] JAVA8_OPTS = new String[]{
|
||||||
|
"-libraryjars '<java.home>/lib/rt.jar'",
|
||||||
|
"-libraryjars '<java.home>/lib/jce.jar'",
|
||||||
|
"-libraryjars '<java.home>/lib/ext/nashorn.jar'",
|
||||||
|
"-libraryjars '<java.home>/lib/ext/jfxrt.jar'"
|
||||||
|
};
|
||||||
private static final char[] chars = "1aAbBcC2dDeEfF3gGhHiI4jJkKl5mMnNoO6pPqQrR7sStT8uUvV9wWxX0yYzZ".toCharArray();
|
private static final char[] chars = "1aAbBcC2dDeEfF3gGhHiI4jJkKl5mMnNoO6pPqQrR7sStT8uUvV9wWxX0yYzZ".toCharArray();
|
||||||
|
|
||||||
public static final String[] JAVA9_OPTS = new String[] {
|
|
||||||
"-libraryjars '<java.home>/jmods/'"
|
|
||||||
};
|
|
||||||
public static final String[] JAVA8_OPTS = new String[] {
|
|
||||||
"-libraryjars '<java.home>/lib/rt.jar'",
|
|
||||||
"-libraryjars '<java.home>/lib/jce.jar'",
|
|
||||||
"-libraryjars '<java.home>/lib/ext/nashorn.jar'",
|
|
||||||
"-libraryjars '<java.home>/lib/ext/jfxrt.jar'"
|
|
||||||
};
|
|
||||||
private static String generateString(SecureRandom rand, String lowString, String upString, int il) {
|
|
||||||
StringBuilder sb = new StringBuilder(Math.max(il, lowString.length()));
|
|
||||||
for (int i = 0; i < lowString.length(); ++i) {
|
|
||||||
sb.append(rand.nextBoolean() ? lowString.charAt(i) : upString.charAt(i));
|
|
||||||
}
|
|
||||||
int toI = il - lowString.length();
|
|
||||||
for (int i = 0; i < toI; i++) sb.append(chars[rand.nextInt(chars.length)]);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Path proguard;
|
public final Path proguard;
|
||||||
public final Path config;
|
public final Path config;
|
||||||
public final Path mappings;
|
public final Path mappings;
|
||||||
|
@ -49,6 +38,16 @@ public ProguardConf(LaunchServer srv) {
|
||||||
this.srv = srv;
|
this.srv = srv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String generateString(SecureRandom rand, String lowString, String upString, int il) {
|
||||||
|
StringBuilder sb = new StringBuilder(Math.max(il, lowString.length()));
|
||||||
|
for (int i = 0; i < lowString.length(); ++i) {
|
||||||
|
sb.append(rand.nextBoolean() ? lowString.charAt(i) : upString.charAt(i));
|
||||||
|
}
|
||||||
|
int toI = il - lowString.length();
|
||||||
|
for (int i = 0; i < toI; i++) sb.append(chars[rand.nextInt(chars.length)]);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public String[] buildConfig(Path inputJar, Path outputJar) {
|
public String[] buildConfig(Path inputJar, Path outputJar) {
|
||||||
List<String> confStrs = new ArrayList<>();
|
List<String> confStrs = new ArrayList<>();
|
||||||
prepare(false);
|
prepare(false);
|
||||||
|
@ -57,7 +56,7 @@ public String[] buildConfig(Path inputJar, Path outputJar) {
|
||||||
confStrs.add("-obfuscationdictionary \'" + words.toFile().getName() + "\'");
|
confStrs.add("-obfuscationdictionary \'" + words.toFile().getName() + "\'");
|
||||||
confStrs.add("-injar \'" + inputJar.toAbsolutePath() + "\'");
|
confStrs.add("-injar \'" + inputJar.toAbsolutePath() + "\'");
|
||||||
confStrs.add("-outjar \'" + outputJar.toAbsolutePath() + "\'");
|
confStrs.add("-outjar \'" + outputJar.toAbsolutePath() + "\'");
|
||||||
Collections.addAll(confStrs, JVMHelper.JVM_VERSION >= 9 ? JAVA9_OPTS : JAVA8_OPTS);
|
Collections.addAll(confStrs, JVMHelper.JVM_VERSION >= 9 ? JAVA9_OPTS : JAVA8_OPTS);
|
||||||
srv.launcherBinary.coreLibs.stream()
|
srv.launcherBinary.coreLibs.stream()
|
||||||
.map(e -> "-libraryjars \'" + e.toAbsolutePath().toString() + "\'")
|
.map(e -> "-libraryjars \'" + e.toAbsolutePath().toString() + "\'")
|
||||||
.forEach(confStrs::add);
|
.forEach(confStrs::add);
|
||||||
|
|
|
@ -40,22 +40,19 @@
|
||||||
*/
|
*/
|
||||||
public class SignerJar implements AutoCloseable {
|
public class SignerJar implements AutoCloseable {
|
||||||
|
|
||||||
private static final String MANIFEST_FN = "META-INF/MANIFEST.MF";
|
private static final String MANIFEST_FN = "META-INF/MANIFEST.MF";
|
||||||
|
private static final String DIGEST_HASH = SignHelper.hashFunctionName + "-Digest";
|
||||||
private final String SIG_FN;
|
private final String SIG_FN;
|
||||||
private final String SIG_KEY_FN;
|
private final String SIG_KEY_FN;
|
||||||
private static final String DIGEST_HASH = SignHelper.hashFunctionName + "-Digest";
|
|
||||||
|
|
||||||
private final ZipOutputStream zos;
|
private final ZipOutputStream zos;
|
||||||
|
|
||||||
private final Map<String, String> manifestAttributes;
|
private final Map<String, String> manifestAttributes;
|
||||||
|
private final Map<String, String> fileDigests;
|
||||||
|
private final Map<String, String> sectionDigests;
|
||||||
|
private final Supplier<CMSSignedDataGenerator> gen;
|
||||||
private String manifestHash;
|
private String manifestHash;
|
||||||
private String manifestMainHash;
|
private String manifestMainHash;
|
||||||
|
|
||||||
private final Map<String, String> fileDigests;
|
|
||||||
|
|
||||||
private final Map<String, String> sectionDigests;
|
|
||||||
private final Supplier<CMSSignedDataGenerator> gen;
|
|
||||||
|
|
||||||
public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, String sig_fn, String sig_key_fn) {
|
public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, String sig_fn, String sig_key_fn) {
|
||||||
zos = out;
|
zos = out;
|
||||||
this.gen = gen;
|
this.gen = gen;
|
||||||
|
@ -76,7 +73,7 @@ public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, Stri
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(String filename, byte[] contents) throws IOException {
|
public void addFileContents(String filename, byte[] contents) throws IOException {
|
||||||
addFileContents(filename, new ByteArrayInputStream(contents));
|
addFileContents(filename, new ByteArrayInputStream(contents));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +86,7 @@ public void addFileContents(String filename, byte[] contents) throws IOException
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(String filename, InputStream contents) throws IOException {
|
public void addFileContents(String filename, InputStream contents) throws IOException {
|
||||||
addFileContents(IOHelper.newZipEntry(filename), contents);
|
addFileContents(IOHelper.newZipEntry(filename), contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +99,7 @@ public void addFileContents(String filename, InputStream contents) throws IOExce
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
|
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
|
||||||
addFileContents(entry, new ByteArrayInputStream(contents));
|
addFileContents(entry, new ByteArrayInputStream(contents));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,7 +135,7 @@ public void addManifestAttribute(String name, String value) {
|
||||||
* underlying stream.
|
* underlying stream.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RuntimeException if the signing goes wrong
|
* @throws RuntimeException if the signing goes wrong
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
|
@ -152,7 +149,7 @@ public void close() throws IOException {
|
||||||
* underlying stream open.
|
* underlying stream open.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RuntimeException if the signing goes wrong
|
* @throws RuntimeException if the signing goes wrong
|
||||||
*/
|
*/
|
||||||
public void finish() throws IOException {
|
public void finish() throws IOException {
|
||||||
writeManifest();
|
writeManifest();
|
||||||
|
@ -272,7 +269,7 @@ private byte[] writeSigFile() throws IOException {
|
||||||
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
|
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws RuntimeException if the signing failed
|
* @throws RuntimeException if the signing failed
|
||||||
*/
|
*/
|
||||||
private void writeSignature(byte[] sigFile) throws IOException {
|
private void writeSignature(byte[] sigFile) throws IOException {
|
||||||
zos.putNextEntry(IOHelper.newZipEntry(SIG_KEY_FN));
|
zos.putNextEntry(IOHelper.newZipEntry(SIG_KEY_FN));
|
||||||
|
|
|
@ -24,20 +24,6 @@ public AdditionalFixesApplyTask(LaunchServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "AdditionalFixesApply";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path process(Path inputFile) throws IOException {
|
|
||||||
Path out = server.launcherBinary.nextPath("post-fixed");
|
|
||||||
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
|
|
||||||
apply(inputFile, inputFile, output, server, (e) -> false, true);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void apply(Path inputFile, Path addFile, ZipOutputStream output, LaunchServer srv, Predicate<ZipEntry> excluder, boolean needFixes) throws IOException {
|
public static void apply(Path inputFile, Path addFile, ZipOutputStream output, LaunchServer srv, Predicate<ZipEntry> excluder, boolean needFixes) throws IOException {
|
||||||
try (ClassMetadataReader reader = new ClassMetadataReader()) {
|
try (ClassMetadataReader reader = new ClassMetadataReader()) {
|
||||||
reader.getCp().add(new JarFile(inputFile.toFile()));
|
reader.getCp().add(new JarFile(inputFile.toFile()));
|
||||||
|
@ -52,11 +38,10 @@ public static void apply(Path inputFile, Path addFile, ZipOutputStream output, L
|
||||||
output.putNextEntry(IOHelper.newZipEntry(e));
|
output.putNextEntry(IOHelper.newZipEntry(e));
|
||||||
if (filename.endsWith(".class")) {
|
if (filename.endsWith(".class")) {
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
if(needFixes) {
|
if (needFixes) {
|
||||||
bytes = classFix(input, reader, srv.config.launcher.stripLineNumbers);
|
bytes = classFix(input, reader, srv.config.launcher.stripLineNumbers);
|
||||||
output.write(bytes);
|
output.write(bytes);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
IOHelper.transfer(input, output);
|
IOHelper.transfer(input, output);
|
||||||
} else
|
} else
|
||||||
IOHelper.transfer(input, output);
|
IOHelper.transfer(input, output);
|
||||||
|
@ -75,6 +60,20 @@ private static byte[] classFix(InputStream input, ClassMetadataReader reader, bo
|
||||||
return cw.toByteArray();
|
return cw.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "AdditionalFixesApply";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path process(Path inputFile) throws IOException {
|
||||||
|
Path out = server.launcherBinary.nextPath("post-fixed");
|
||||||
|
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
|
||||||
|
apply(inputFile, inputFile, output, server, (e) -> false, true);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean allowDelete() {
|
public boolean allowDelete() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
import org.bouncycastle.asn1.x509.*;
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
@ -29,6 +32,9 @@
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class CertificateAutogenTask implements LauncherBuildTask {
|
public class CertificateAutogenTask implements LauncherBuildTask {
|
||||||
|
public X509Certificate certificate;
|
||||||
|
public X509CertificateHolder bcCertificate;
|
||||||
|
public CMSSignedDataGenerator signedDataGenerator;
|
||||||
private LaunchServer server;
|
private LaunchServer server;
|
||||||
|
|
||||||
public CertificateAutogenTask(LaunchServer server) {
|
public CertificateAutogenTask(LaunchServer server) {
|
||||||
|
@ -39,13 +45,10 @@ public CertificateAutogenTask(LaunchServer server) {
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "CertificateAutogen";
|
return "CertificateAutogen";
|
||||||
}
|
}
|
||||||
public X509Certificate certificate;
|
|
||||||
public X509CertificateHolder bcCertificate;
|
|
||||||
public CMSSignedDataGenerator signedDataGenerator;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path process(Path inputFile) throws IOException {
|
public Path process(Path inputFile) throws IOException {
|
||||||
if(signedDataGenerator != null) return inputFile;
|
if (signedDataGenerator != null) return inputFile;
|
||||||
try {
|
try {
|
||||||
X500NameBuilder subject = new X500NameBuilder();
|
X500NameBuilder subject = new X500NameBuilder();
|
||||||
subject.addRDN(BCStyle.CN, server.config.projectName.concat(" Autogenerated"));
|
subject.addRDN(BCStyle.CN, server.config.projectName.concat(" Autogenerated"));
|
||||||
|
@ -63,8 +66,8 @@ public Path process(Path inputFile) throws IOException {
|
||||||
JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256WITHECDSA");
|
JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256WITHECDSA");
|
||||||
ContentSigner signer = csBuilder.build(server.privateKey);
|
ContentSigner signer = csBuilder.build(server.privateKey);
|
||||||
bcCertificate = builder.build(signer);
|
bcCertificate = builder.build(signer);
|
||||||
certificate = new JcaX509CertificateConverter().setProvider( "BC" )
|
certificate = new JcaX509CertificateConverter().setProvider("BC")
|
||||||
.getCertificate( bcCertificate );
|
.getCertificate(bcCertificate);
|
||||||
ArrayList<Certificate> chain = new ArrayList<>();
|
ArrayList<Certificate> chain = new ArrayList<>();
|
||||||
chain.add(certificate);
|
chain.add(certificate);
|
||||||
signedDataGenerator = SignHelper.createSignedDataGenerator(server.privateKey, certificate, chain, "SHA256WITHECDSA");
|
signedDataGenerator = SignHelper.createSignedDataGenerator(server.privateKey, certificate, chain, "SHA256WITHECDSA");
|
||||||
|
|
|
@ -27,94 +27,12 @@
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
public class MainBuildTask implements LauncherBuildTask {
|
public class MainBuildTask implements LauncherBuildTask {
|
||||||
private final LaunchServer server;
|
|
||||||
public final ClassMetadataReader reader;
|
public final ClassMetadataReader reader;
|
||||||
@FunctionalInterface
|
private final LaunchServer server;
|
||||||
public interface Transformer {
|
|
||||||
byte[] transform(byte[] input, String classname, BuildContext context);
|
|
||||||
}
|
|
||||||
public static class IOHookSet<R> {
|
|
||||||
public final Set<IOHook<R>> list = new HashSet<>();
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface IOHook<R> {
|
|
||||||
/**
|
|
||||||
* @param context custom param
|
|
||||||
* False to continue processing hook
|
|
||||||
* @throws HookException The hook may return the error text throwing this exception
|
|
||||||
*/
|
|
||||||
void hook(R context) throws HookException, IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerHook(IOHook<R> hook) {
|
|
||||||
list.add(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean unregisterHook(IOHook<R> hook) {
|
|
||||||
return list.remove(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context custom param
|
|
||||||
* False to continue
|
|
||||||
* @throws HookException The hook may return the error text throwing this exception
|
|
||||||
*/
|
|
||||||
public void hook(R context) throws HookException, IOException {
|
|
||||||
for (IOHook<R> hook : list) {
|
|
||||||
hook.hook(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ASMTransformer extends Transformer {
|
|
||||||
default byte[] transform(byte[] input, String classname, BuildContext context)
|
|
||||||
{
|
|
||||||
ClassReader reader = new ClassReader(input);
|
|
||||||
ClassNode cn = new ClassNode();
|
|
||||||
reader.accept(cn, 0);
|
|
||||||
transform(cn, classname, context);
|
|
||||||
SafeClassWriter writer = new SafeClassWriter(context.task.reader,ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
|
||||||
cn.accept(writer);
|
|
||||||
return writer.toByteArray();
|
|
||||||
}
|
|
||||||
void transform(ClassNode cn, String classname, BuildContext context);
|
|
||||||
}
|
|
||||||
public abstract static class ASMAnnotationFieldProcessor implements ASMTransformer
|
|
||||||
{
|
|
||||||
private final String desc;
|
|
||||||
|
|
||||||
protected ASMAnnotationFieldProcessor(String desc) {
|
|
||||||
this.desc = desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void transform(ClassNode cn, String classname, BuildContext context) {
|
|
||||||
for(FieldNode fn : cn.fields)
|
|
||||||
{
|
|
||||||
if(fn.invisibleAnnotations == null || fn.invisibleAnnotations.isEmpty()) continue;
|
|
||||||
AnnotationNode found = null;
|
|
||||||
for(AnnotationNode an : fn.invisibleAnnotations)
|
|
||||||
{
|
|
||||||
if(an == null) continue;
|
|
||||||
if(desc.equals(an.desc))
|
|
||||||
{
|
|
||||||
found = an;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(found != null)
|
|
||||||
{
|
|
||||||
transformField(found, fn, cn, classname, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
abstract public void transformField(AnnotationNode an, FieldNode fn, ClassNode cn, String classname, BuildContext context);
|
|
||||||
}
|
|
||||||
public Set<String> blacklist = new HashSet<>();
|
public Set<String> blacklist = new HashSet<>();
|
||||||
public List<Transformer> transformers = new ArrayList<>();
|
public List<Transformer> transformers = new ArrayList<>();
|
||||||
public IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
|
public IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
|
||||||
public IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
|
public IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
|
||||||
|
|
||||||
public Map<String, Object> properties = new HashMap<>();
|
public Map<String, Object> properties = new HashMap<>();
|
||||||
|
|
||||||
public MainBuildTask(LaunchServer srv) {
|
public MainBuildTask(LaunchServer srv) {
|
||||||
|
@ -163,7 +81,7 @@ public Path process(Path inputJar) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void postInitProps() {
|
protected void postInitProps() {
|
||||||
List<byte[]> certificates = Arrays.stream(server.certificateManager.trustManager.getTrusted()).map(e -> {
|
List<byte[]> certificates = Arrays.stream(server.certificateManager.trustManager.getTrusted()).map(e -> {
|
||||||
try {
|
try {
|
||||||
return e.getEncoded();
|
return e.getEncoded();
|
||||||
} catch (CertificateEncodingException e2) {
|
} catch (CertificateEncodingException e2) {
|
||||||
|
@ -171,8 +89,7 @@ protected void postInitProps() {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
if(!server.config.sign.enabled)
|
if (!server.config.sign.enabled) {
|
||||||
{
|
|
||||||
CertificateAutogenTask task = server.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
|
CertificateAutogenTask task = server.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
|
||||||
try {
|
try {
|
||||||
certificates.add(task.certificate.getEncoded());
|
certificates.add(task.certificate.getEncoded());
|
||||||
|
@ -181,10 +98,10 @@ protected void postInitProps() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties.put("launchercore.certificates", certificates);
|
properties.put("launchercore.certificates", certificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initProps() {
|
protected void initProps() {
|
||||||
properties.clear();
|
properties.clear();
|
||||||
properties.put("launcher.address", server.config.netty.address);
|
properties.put("launcher.address", server.config.netty.address);
|
||||||
properties.put("launcher.projectName", server.config.projectName);
|
properties.put("launcher.projectName", server.config.projectName);
|
||||||
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
||||||
|
@ -202,45 +119,37 @@ protected void initProps() {
|
||||||
if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
|
if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
|
||||||
properties.put("runtimeconfig.oemUnlockKey", server.runtime.oemUnlockKey);
|
properties.put("runtimeconfig.oemUnlockKey", server.runtime.oemUnlockKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] transformClass(byte[] bytes, String classname, BuildContext context)
|
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
|
||||||
{
|
|
||||||
byte[] result = bytes;
|
byte[] result = bytes;
|
||||||
ClassReader cr = null;
|
ClassReader cr = null;
|
||||||
ClassWriter writer = null;
|
ClassWriter writer = null;
|
||||||
ClassNode cn = null;
|
ClassNode cn = null;
|
||||||
for(Transformer t : transformers)
|
for (Transformer t : transformers) {
|
||||||
{
|
if (t instanceof ASMTransformer) {
|
||||||
if(t instanceof ASMTransformer)
|
|
||||||
{
|
|
||||||
ASMTransformer asmTransformer = (ASMTransformer) t;
|
ASMTransformer asmTransformer = (ASMTransformer) t;
|
||||||
if(cn == null)
|
if (cn == null) {
|
||||||
{
|
|
||||||
cr = new ClassReader(result);
|
cr = new ClassReader(result);
|
||||||
cn = new ClassNode();
|
cn = new ClassNode();
|
||||||
cr.accept(cn, 0);
|
cr.accept(cn, 0);
|
||||||
}
|
}
|
||||||
asmTransformer.transform(cn, classname, context);
|
asmTransformer.transform(cn, classname, context);
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (cn != null) {
|
||||||
else if(cn != null)
|
|
||||||
{
|
|
||||||
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||||
cn.accept(writer);
|
cn.accept(writer);
|
||||||
result = writer.toByteArray();
|
result = writer.toByteArray();
|
||||||
}
|
}
|
||||||
byte[] old_result = result;
|
byte[] old_result = result;
|
||||||
result = t.transform(result, classname, context);
|
result = t.transform(result, classname, context);
|
||||||
if(old_result != result)
|
if (old_result != result) {
|
||||||
{
|
|
||||||
cr = null;
|
cr = null;
|
||||||
cn = null;
|
cn = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(cn != null)
|
if (cn != null) {
|
||||||
{
|
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||||
writer = new SafeClassWriter(reader,ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
|
||||||
cn.accept(writer);
|
cn.accept(writer);
|
||||||
result = writer.toByteArray();
|
result = writer.toByteArray();
|
||||||
}
|
}
|
||||||
|
@ -251,4 +160,84 @@ else if(cn != null)
|
||||||
public boolean allowDelete() {
|
public boolean allowDelete() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Transformer {
|
||||||
|
byte[] transform(byte[] input, String classname, BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ASMTransformer extends Transformer {
|
||||||
|
default byte[] transform(byte[] input, String classname, BuildContext context) {
|
||||||
|
ClassReader reader = new ClassReader(input);
|
||||||
|
ClassNode cn = new ClassNode();
|
||||||
|
reader.accept(cn, 0);
|
||||||
|
transform(cn, classname, context);
|
||||||
|
SafeClassWriter writer = new SafeClassWriter(context.task.reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||||
|
cn.accept(writer);
|
||||||
|
return writer.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void transform(ClassNode cn, String classname, BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IOHookSet<R> {
|
||||||
|
public final Set<IOHook<R>> list = new HashSet<>();
|
||||||
|
|
||||||
|
public void registerHook(IOHook<R> hook) {
|
||||||
|
list.add(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean unregisterHook(IOHook<R> hook) {
|
||||||
|
return list.remove(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context custom param
|
||||||
|
* False to continue
|
||||||
|
* @throws HookException The hook may return the error text throwing this exception
|
||||||
|
*/
|
||||||
|
public void hook(R context) throws HookException, IOException {
|
||||||
|
for (IOHook<R> hook : list) {
|
||||||
|
hook.hook(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface IOHook<R> {
|
||||||
|
/**
|
||||||
|
* @param context custom param
|
||||||
|
* False to continue processing hook
|
||||||
|
* @throws HookException The hook may return the error text throwing this exception
|
||||||
|
*/
|
||||||
|
void hook(R context) throws HookException, IOException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class ASMAnnotationFieldProcessor implements ASMTransformer {
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
protected ASMAnnotationFieldProcessor(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(ClassNode cn, String classname, BuildContext context) {
|
||||||
|
for (FieldNode fn : cn.fields) {
|
||||||
|
if (fn.invisibleAnnotations == null || fn.invisibleAnnotations.isEmpty()) continue;
|
||||||
|
AnnotationNode found = null;
|
||||||
|
for (AnnotationNode an : fn.invisibleAnnotations) {
|
||||||
|
if (an == null) continue;
|
||||||
|
if (desc.equals(an.desc)) {
|
||||||
|
found = an;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found != null) {
|
||||||
|
transformField(found, fn, cn, classname, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public void transformField(AnnotationNode an, FieldNode fn, ClassNode cn, String classname, BuildContext context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,17 @@ public SignJarTask(LaunchServerConfig.JarSignerConf config, LaunchServer srv) {
|
||||||
this.srv = srv;
|
this.srv = srv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CMSSignedDataGenerator gen(LaunchServerConfig.JarSignerConf config, KeyStore c) {
|
||||||
|
try {
|
||||||
|
return SignHelper.createSignedDataGenerator(c,
|
||||||
|
config.keyAlias, config.signAlgo, config.keyPass);
|
||||||
|
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
|
||||||
|
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "SignJar";
|
return "SignJar";
|
||||||
|
@ -46,7 +57,7 @@ public Path process(Path inputFile) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sign(LaunchServerConfig.JarSignerConf config, Path inputFile, Path signedFile) throws IOException {
|
public void sign(LaunchServerConfig.JarSignerConf config, Path inputFile, Path signedFile) throws IOException {
|
||||||
if(config.enabled) stdSign(config, inputFile, signedFile);
|
if (config.enabled) stdSign(config, inputFile, signedFile);
|
||||||
else autoSign(inputFile, signedFile);
|
else autoSign(inputFile, signedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +80,7 @@ private void stdSign(LaunchServerConfig.JarSignerConf config, Path inputFile, Pa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoSign(Path inputFile, Path signedFile) throws IOException {
|
private void autoSign(Path inputFile, Path signedFile) throws IOException {
|
||||||
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> {
|
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> {
|
||||||
CertificateAutogenTask task = srv.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
|
CertificateAutogenTask task = srv.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
|
||||||
|
@ -95,15 +107,4 @@ private void autoSign(Path inputFile, Path signedFile) throws IOException {
|
||||||
public boolean allowDelete() {
|
public boolean allowDelete() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CMSSignedDataGenerator gen(LaunchServerConfig.JarSignerConf config, KeyStore c) {
|
|
||||||
try {
|
|
||||||
return SignHelper.createSignedDataGenerator(c,
|
|
||||||
config.keyAlias, config.signAlgo, config.keyPass);
|
|
||||||
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
|
|
||||||
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
|
|
||||||
LogHelper.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,23 +13,10 @@
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class Launch4JTask implements LauncherBuildTask {
|
public class Launch4JTask implements LauncherBuildTask {
|
||||||
private final static class Launch4JLog extends Log {
|
|
||||||
private static final Launch4JLog INSTANCE = new Launch4JLog();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(String s) {
|
|
||||||
LogHelper.subInfo(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle
|
public static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle
|
||||||
|
private static final String VERSION = Version.getVersion().getVersionString();
|
||||||
|
private static final int BUILD = Version.getVersion().build;
|
||||||
private final Path faviconFile;
|
private final Path faviconFile;
|
||||||
|
|
||||||
private final LaunchServer server;
|
private final LaunchServer server;
|
||||||
|
|
||||||
public Launch4JTask(LaunchServer launchServer) {
|
public Launch4JTask(LaunchServer launchServer) {
|
||||||
|
@ -37,6 +24,10 @@ public Launch4JTask(LaunchServer launchServer) {
|
||||||
faviconFile = launchServer.dir.resolve("favicon.ico");
|
faviconFile = launchServer.dir.resolve("favicon.ico");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatVars(String mask) {
|
||||||
|
return String.format(mask, VERSION, BUILD);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "launch4j";
|
return "launch4j";
|
||||||
|
@ -70,6 +61,7 @@ public Path process(Path inputFile) throws IOException {
|
||||||
public boolean allowDelete() {
|
public boolean allowDelete() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path setConfig() {
|
private Path setConfig() {
|
||||||
Path path = server.launcherEXEBinary.nextPath(getName());
|
Path path = server.launcherEXEBinary.nextPath(getName());
|
||||||
Config config = new Config();
|
Config config = new Config();
|
||||||
|
@ -120,10 +112,17 @@ private Path setConfig() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String VERSION = Version.getVersion().getVersionString();
|
private final static class Launch4JLog extends Log {
|
||||||
private static final int BUILD = Version.getVersion().build;
|
private static final Launch4JLog INSTANCE = new Launch4JLog();
|
||||||
|
|
||||||
public static String formatVars(String mask) {
|
@Override
|
||||||
return String.format(mask, VERSION, BUILD);
|
public void append(String s) {
|
||||||
|
LogHelper.subInfo(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
|
||||||
public class TestCommand extends Command {
|
public class TestCommand extends Command {
|
||||||
|
private NettyServerSocketHandler handler = null;
|
||||||
|
|
||||||
public TestCommand(LaunchServer server) {
|
public TestCommand(LaunchServer server) {
|
||||||
super(server);
|
super(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NettyServerSocketHandler handler = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getArgsDescription() {
|
public String getArgsDescription() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -20,65 +20,23 @@
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public final class IndexAssetCommand extends Command {
|
public final class IndexAssetCommand extends Command {
|
||||||
private static final Gson gson = new Gson();
|
|
||||||
|
|
||||||
public static class IndexObject {
|
|
||||||
final long size;
|
|
||||||
|
|
||||||
public IndexObject(long size, String hash) {
|
|
||||||
this.size = size;
|
|
||||||
this.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
private final JsonObject objects;
|
|
||||||
private final Path inputAssetDir;
|
|
||||||
private final Path outputAssetDir;
|
|
||||||
|
|
||||||
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) {
|
|
||||||
this.objects = objects;
|
|
||||||
this.inputAssetDir = inputAssetDir;
|
|
||||||
this.outputAssetDir = outputAssetDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
String name = IOHelper.toString(inputAssetDir.relativize(file));
|
|
||||||
LogHelper.subInfo("Indexing: '%s'", name);
|
|
||||||
|
|
||||||
// Add to index and copy file
|
|
||||||
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
|
|
||||||
IndexObject obj = new IndexObject(attrs.size(), digest);
|
|
||||||
objects.add(name, gson.toJsonTree(obj));
|
|
||||||
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest));
|
|
||||||
|
|
||||||
// Continue visiting
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String INDEXES_DIR = "indexes";
|
public static final String INDEXES_DIR = "indexes";
|
||||||
public static final String OBJECTS_DIR = "objects";
|
public static final String OBJECTS_DIR = "objects";
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
private static final String JSON_EXTENSION = ".json";
|
private static final String JSON_EXTENSION = ".json";
|
||||||
|
|
||||||
|
public IndexAssetCommand(LaunchServer server) {
|
||||||
|
super(server);
|
||||||
|
}
|
||||||
|
|
||||||
public static Path resolveIndexFile(Path assetDir, String name) {
|
public static Path resolveIndexFile(Path assetDir, String name) {
|
||||||
return assetDir.resolve(INDEXES_DIR).resolve(name + JSON_EXTENSION);
|
return assetDir.resolve(INDEXES_DIR).resolve(name + JSON_EXTENSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path resolveObjectFile(Path assetDir, String hash) {
|
public static Path resolveObjectFile(Path assetDir, String hash) {
|
||||||
return assetDir.resolve(OBJECTS_DIR).resolve(hash.substring(0, 2)).resolve(hash);
|
return assetDir.resolve(OBJECTS_DIR).resolve(hash.substring(0, 2)).resolve(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexAssetCommand(LaunchServer server) {
|
|
||||||
super(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getArgsDescription() {
|
public String getArgsDescription() {
|
||||||
return "[dir] [index] [output-dir]";
|
return "[dir] [index] [output-dir]";
|
||||||
|
@ -122,4 +80,41 @@ public void invoke(String... args) throws Exception {
|
||||||
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
|
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
|
||||||
LogHelper.subInfo("Asset successfully indexed: '%s'", inputAssetDirName);
|
LogHelper.subInfo("Asset successfully indexed: '%s'", inputAssetDirName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class IndexObject {
|
||||||
|
final long size;
|
||||||
|
final String hash;
|
||||||
|
|
||||||
|
public IndexObject(long size, String hash) {
|
||||||
|
this.size = size;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private final JsonObject objects;
|
||||||
|
private final Path inputAssetDir;
|
||||||
|
private final Path outputAssetDir;
|
||||||
|
|
||||||
|
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) {
|
||||||
|
this.objects = objects;
|
||||||
|
this.inputAssetDir = inputAssetDir;
|
||||||
|
this.outputAssetDir = outputAssetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
String name = IOHelper.toString(inputAssetDir.relativize(file));
|
||||||
|
LogHelper.subInfo("Indexing: '%s'", name);
|
||||||
|
|
||||||
|
// Add to index and copy file
|
||||||
|
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
|
||||||
|
IndexObject obj = new IndexObject(attrs.size(), digest);
|
||||||
|
objects.add(name, gson.toJsonTree(obj));
|
||||||
|
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest));
|
||||||
|
|
||||||
|
// Continue visiting
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,13 @@ public SaveProfilesCommand(LaunchServer server) {
|
||||||
super(server);
|
super(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void saveProfile(ClientProfile profile, Path path) throws IOException {
|
||||||
|
if (profile.getUUID() == null) profile.setUUID(UUID.randomUUID());
|
||||||
|
try (Writer w = IOHelper.newWriter(path)) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(profile, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getArgsDescription() {
|
public String getArgsDescription() {
|
||||||
return "[profile names...]";
|
return "[profile names...]";
|
||||||
|
@ -32,19 +39,15 @@ public String getUsageDescription() {
|
||||||
@Override
|
@Override
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 1);
|
verifyArgs(args, 1);
|
||||||
if(args.length > 0)
|
if (args.length > 0) {
|
||||||
{
|
for (String profileName : args) {
|
||||||
for(String profileName : args)
|
|
||||||
{
|
|
||||||
Path profilePath = server.profilesDir.resolve(profileName.concat(".json"));
|
Path profilePath = server.profilesDir.resolve(profileName.concat(".json"));
|
||||||
if(!Files.exists(profilePath))
|
if (!Files.exists(profilePath)) {
|
||||||
{
|
|
||||||
LogHelper.error("Profile %s not found", profilePath.toString());
|
LogHelper.error("Profile %s not found", profilePath.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClientProfile profile;
|
ClientProfile profile;
|
||||||
try(Reader reader = IOHelper.newReader(profilePath))
|
try (Reader reader = IOHelper.newReader(profilePath)) {
|
||||||
{
|
|
||||||
profile = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
|
profile = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
|
||||||
}
|
}
|
||||||
saveProfile(profile, profilePath);
|
saveProfile(profile, profilePath);
|
||||||
|
@ -53,12 +56,4 @@ public void invoke(String... args) throws Exception {
|
||||||
server.syncProfilesDir();
|
server.syncProfilesDir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static void saveProfile(ClientProfile profile, Path path) throws IOException
|
|
||||||
{
|
|
||||||
if(profile.getUUID() == null) profile.setUUID(UUID.randomUUID());
|
|
||||||
try(Writer w = IOHelper.newWriter(path))
|
|
||||||
{
|
|
||||||
Launcher.gsonManager.configGson.toJson(profile, w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,26 +12,6 @@
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class SignDirCommand extends Command {
|
public class SignDirCommand extends Command {
|
||||||
private class SignJarVisitor extends SimpleFileVisitor<Path>
|
|
||||||
{
|
|
||||||
private SignJarTask task;
|
|
||||||
public SignJarVisitor(SignJarTask task) {
|
|
||||||
this.task = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
if (file.toFile().getName().endsWith(".jar"))
|
|
||||||
{
|
|
||||||
Path tmpSign = server.dir.resolve("build").resolve(file.toFile().getName());
|
|
||||||
LogHelper.info("Signing jar %s", file.toString());
|
|
||||||
task.sign(server.config.sign, file, tmpSign);
|
|
||||||
Files.deleteIfExists(file);
|
|
||||||
Files.move(tmpSign, file);
|
|
||||||
}
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public SignDirCommand(LaunchServer server) {
|
public SignDirCommand(LaunchServer server) {
|
||||||
super(server);
|
super(server);
|
||||||
}
|
}
|
||||||
|
@ -50,11 +30,31 @@ public String getUsageDescription() {
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 1);
|
verifyArgs(args, 1);
|
||||||
Path targetDir = Paths.get(args[0]);
|
Path targetDir = Paths.get(args[0]);
|
||||||
if(!IOHelper.isDir(targetDir))
|
if (!IOHelper.isDir(targetDir))
|
||||||
throw new IllegalArgumentException(String.format("%s not directory", targetDir.toString()));
|
throw new IllegalArgumentException(String.format("%s not directory", targetDir.toString()));
|
||||||
Optional<SignJarTask> task = server.launcherBinary.getTaskByClass(SignJarTask.class);
|
Optional<SignJarTask> task = server.launcherBinary.getTaskByClass(SignJarTask.class);
|
||||||
if(!task.isPresent()) throw new IllegalStateException("SignJarTask not found");
|
if (!task.isPresent()) throw new IllegalStateException("SignJarTask not found");
|
||||||
IOHelper.walk(targetDir, new SignJarVisitor(task.get()), true);
|
IOHelper.walk(targetDir, new SignJarVisitor(task.get()), true);
|
||||||
LogHelper.info("Success signed");
|
LogHelper.info("Success signed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SignJarVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private SignJarTask task;
|
||||||
|
|
||||||
|
public SignJarVisitor(SignJarTask task) {
|
||||||
|
this.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (file.toFile().getName().endsWith(".jar")) {
|
||||||
|
Path tmpSign = server.dir.resolve("build").resolve(file.toFile().getName());
|
||||||
|
LogHelper.info("Signing jar %s", file.toString());
|
||||||
|
task.sign(server.config.sign, file, tmpSign);
|
||||||
|
Files.deleteIfExists(file);
|
||||||
|
Files.move(tmpSign, file);
|
||||||
|
}
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,15 @@ public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 1);
|
verifyArgs(args, 1);
|
||||||
Path target = Paths.get(args[0]);
|
Path target = Paths.get(args[0]);
|
||||||
Path tmpSign;
|
Path tmpSign;
|
||||||
if(args.length > 1)
|
if (args.length > 1)
|
||||||
tmpSign = Paths.get(args[1]);
|
tmpSign = Paths.get(args[1]);
|
||||||
else
|
else
|
||||||
tmpSign = server.dir.resolve("build").resolve(target.toFile().getName());
|
tmpSign = server.dir.resolve("build").resolve(target.toFile().getName());
|
||||||
LogHelper.info("Signing jar %s to %s", target.toString(), tmpSign.toString());
|
LogHelper.info("Signing jar %s to %s", target.toString(), tmpSign.toString());
|
||||||
Optional<SignJarTask> task = server.launcherBinary.getTaskByClass(SignJarTask.class);
|
Optional<SignJarTask> task = server.launcherBinary.getTaskByClass(SignJarTask.class);
|
||||||
if(!task.isPresent()) throw new IllegalStateException("SignJarTask not found");
|
if (!task.isPresent()) throw new IllegalStateException("SignJarTask not found");
|
||||||
task.get().sign(server.config.sign, target, tmpSign);
|
task.get().sign(server.config.sign, target, tmpSign);
|
||||||
if(args.length <= 1)
|
if (args.length <= 1) {
|
||||||
{
|
|
||||||
LogHelper.info("Move temp jar %s to %s", tmpSign.toString(), target.toString());
|
LogHelper.info("Move temp jar %s to %s", tmpSign.toString(), target.toString());
|
||||||
Files.deleteIfExists(target);
|
Files.deleteIfExists(target);
|
||||||
Files.move(tmpSign, target);
|
Files.move(tmpSign, target);
|
||||||
|
|
|
@ -12,9 +12,10 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class AbstractLimiter<T> extends Component implements NeedGarbageCollection, Reconfigurable {
|
public abstract class AbstractLimiter<T> extends Component implements NeedGarbageCollection, Reconfigurable {
|
||||||
|
public final List<T> exclude = new ArrayList<>();
|
||||||
|
protected final transient Map<T, LimitEntry> map = new HashMap<>();
|
||||||
public int rateLimit;
|
public int rateLimit;
|
||||||
public int rateLimitMillis;
|
public int rateLimitMillis;
|
||||||
public final List<T> exclude = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Command> getCommands() {
|
public Map<String, Command> getCommands() {
|
||||||
|
@ -67,23 +68,6 @@ public void garbageCollection() {
|
||||||
map.entrySet().removeIf((e) -> e.getValue().time + rateLimitMillis < time);
|
map.entrySet().removeIf((e) -> e.getValue().time + rateLimitMillis < time);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class LimitEntry {
|
|
||||||
long time;
|
|
||||||
int trys;
|
|
||||||
|
|
||||||
public LimitEntry(long time, int trys) {
|
|
||||||
this.time = time;
|
|
||||||
this.trys = trys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LimitEntry() {
|
|
||||||
time = System.currentTimeMillis();
|
|
||||||
trys = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final transient Map<T, LimitEntry> map = new HashMap<>();
|
|
||||||
|
|
||||||
public boolean check(T address) {
|
public boolean check(T address) {
|
||||||
if (exclude.contains(address)) return true;
|
if (exclude.contains(address)) return true;
|
||||||
LimitEntry entry = map.get(address);
|
LimitEntry entry = map.get(address);
|
||||||
|
@ -105,4 +89,19 @@ public boolean check(T address) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class LimitEntry {
|
||||||
|
long time;
|
||||||
|
int trys;
|
||||||
|
|
||||||
|
public LimitEntry(long time, int trys) {
|
||||||
|
this.time = time;
|
||||||
|
this.trys = trys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LimitEntry() {
|
||||||
|
time = System.currentTimeMillis();
|
||||||
|
trys = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import pro.gravit.utils.HookException;
|
import pro.gravit.utils.HookException;
|
||||||
|
|
||||||
public class AuthLimiterComponent extends IPLimiter implements NeedGarbageCollection, AutoCloseable {
|
public class AuthLimiterComponent extends IPLimiter implements NeedGarbageCollection, AutoCloseable {
|
||||||
|
public String message;
|
||||||
private transient LaunchServer srv;
|
private transient LaunchServer srv;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,8 +32,6 @@ public boolean preAuthHook(AuthResponse.AuthContext context, Client client) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String message;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
srv.authHookManager.preHook.unregisterHook(this::preAuthHook);
|
srv.authHookManager.preHook.unregisterHook(this::preAuthHook);
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
import pro.gravit.launcher.LauncherConfig;
|
import pro.gravit.launcher.LauncherConfig;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.Reconfigurable;
|
|
||||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.handler.MemoryAuthHandler;
|
import pro.gravit.launchserver.auth.handler.MemoryAuthHandler;
|
||||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||||
|
@ -22,43 +21,109 @@
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class LaunchServerConfig {
|
public final class LaunchServerConfig {
|
||||||
|
public String projectName;
|
||||||
|
public String[] mirrors;
|
||||||
|
public String binaryName;
|
||||||
|
public boolean copyBinaries = true;
|
||||||
|
public LauncherConfig.LauncherEnvironment env;
|
||||||
|
public Map<String, AuthProviderPair> auth;
|
||||||
|
public DaoProvider dao;
|
||||||
|
|
||||||
|
// Handlers & Providers
|
||||||
|
public ProtectHandler protectHandler;
|
||||||
|
public Map<String, Component> components;
|
||||||
|
public ExeConf launch4j;
|
||||||
|
public NettyConfig netty;
|
||||||
|
public LauncherConf launcher;
|
||||||
|
public CertificateConf certificate;
|
||||||
|
public JarSignerConf sign;
|
||||||
|
public String startScript;
|
||||||
private transient LaunchServer server = null;
|
private transient LaunchServer server = null;
|
||||||
|
private transient AuthProviderPair authDefault;
|
||||||
|
|
||||||
|
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
||||||
|
LaunchServerConfig newConfig = new LaunchServerConfig();
|
||||||
|
newConfig.mirrors = new String[]{"https://mirror.gravit.pro/"};
|
||||||
|
newConfig.launch4j = new LaunchServerConfig.ExeConf();
|
||||||
|
newConfig.launch4j.enabled = true;
|
||||||
|
newConfig.launch4j.copyright = "© GravitLauncher Team";
|
||||||
|
newConfig.launch4j.fileDesc = "GravitLauncher ".concat(Version.getVersion().getVersionString());
|
||||||
|
newConfig.launch4j.fileVer = Version.getVersion().getVersionString().concat(".").concat(String.valueOf(Version.getVersion().patch));
|
||||||
|
newConfig.launch4j.internalName = "Launcher";
|
||||||
|
newConfig.launch4j.trademarks = "This product is licensed under GPLv3";
|
||||||
|
newConfig.launch4j.txtFileVersion = "%s, build %d";
|
||||||
|
newConfig.launch4j.txtProductVersion = "%s, build %d";
|
||||||
|
newConfig.launch4j.productName = "GravitLauncher";
|
||||||
|
newConfig.launch4j.productVer = newConfig.launch4j.fileVer;
|
||||||
|
newConfig.launch4j.maxVersion = "1.8.999";
|
||||||
|
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
|
||||||
|
newConfig.startScript = JVMHelper.OS_TYPE.equals(JVMHelper.OS.MUSTDIE) ? "." + File.separator + "start.bat" : "." + File.separator + "start.sh";
|
||||||
|
newConfig.auth = new HashMap<>();
|
||||||
|
AuthProviderPair a = new AuthProviderPair(new RejectAuthProvider("Настройте authProvider"),
|
||||||
|
new MemoryAuthHandler(),
|
||||||
|
new RequestTextureProvider("http://example.com/skins/%username%.png", "http://example.com/cloaks/%username%.png")
|
||||||
|
);
|
||||||
|
a.displayName = "Default";
|
||||||
|
newConfig.auth.put("std", a);
|
||||||
|
newConfig.protectHandler = new StdProtectHandler();
|
||||||
|
newConfig.binaryName = "Launcher";
|
||||||
|
|
||||||
|
newConfig.netty = new NettyConfig();
|
||||||
|
newConfig.netty.fileServerEnabled = true;
|
||||||
|
newConfig.netty.binds = new NettyBindAddress[]{new NettyBindAddress("0.0.0.0", 9274)};
|
||||||
|
newConfig.netty.performance = new NettyPerformanceConfig();
|
||||||
|
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.launcher = new LauncherConf();
|
||||||
|
newConfig.launcher.guardType = "no";
|
||||||
|
newConfig.launcher.compress = true;
|
||||||
|
newConfig.launcher.warningMissArchJava = true;
|
||||||
|
newConfig.launcher.attachLibraryBeforeProGuard = false;
|
||||||
|
newConfig.launcher.deleteTempFiles = true;
|
||||||
|
newConfig.launcher.enabledProGuard = true;
|
||||||
|
newConfig.launcher.stripLineNumbers = true;
|
||||||
|
newConfig.launcher.proguardGenMappings = true;
|
||||||
|
|
||||||
|
newConfig.certificate = new LaunchServerConfig.CertificateConf();
|
||||||
|
newConfig.certificate.enabled = false;
|
||||||
|
newConfig.sign = new JarSignerConf();
|
||||||
|
|
||||||
|
newConfig.components = new HashMap<>();
|
||||||
|
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
|
||||||
|
authLimiterComponent.rateLimit = 3;
|
||||||
|
authLimiterComponent.rateLimitMillis = 8000;
|
||||||
|
authLimiterComponent.message = "Превышен лимит авторизаций";
|
||||||
|
newConfig.components.put("authLimiter", authLimiterComponent);
|
||||||
|
RegLimiterComponent regLimiterComponent = new RegLimiterComponent();
|
||||||
|
regLimiterComponent.rateLimit = 3;
|
||||||
|
regLimiterComponent.rateLimitMillis = 1000 * 60 * 60 * 10; //Блок на 10 часов
|
||||||
|
regLimiterComponent.message = "Превышен лимит регистраций";
|
||||||
|
newConfig.components.put("regLimiter", regLimiterComponent);
|
||||||
|
newConfig.netty.sendExceptionEnabled = true;
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
public LaunchServerConfig setLaunchServer(LaunchServer server) {
|
public LaunchServerConfig setLaunchServer(LaunchServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String projectName;
|
|
||||||
|
|
||||||
public String[] mirrors;
|
|
||||||
|
|
||||||
public String binaryName;
|
|
||||||
|
|
||||||
public boolean copyBinaries = true;
|
|
||||||
|
|
||||||
public LauncherConfig.LauncherEnvironment env;
|
|
||||||
|
|
||||||
// Handlers & Providers
|
|
||||||
|
|
||||||
public Map<String, AuthProviderPair> auth;
|
|
||||||
|
|
||||||
public DaoProvider dao;
|
|
||||||
|
|
||||||
private transient AuthProviderPair authDefault;
|
|
||||||
|
|
||||||
public AuthProviderPair getAuthProviderPair(String name) {
|
public AuthProviderPair getAuthProviderPair(String name) {
|
||||||
return auth.get(name);
|
return auth.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectHandler protectHandler;
|
|
||||||
|
|
||||||
public AuthProviderPair getAuthProviderPair() {
|
public AuthProviderPair getAuthProviderPair() {
|
||||||
if (authDefault != null) return authDefault;
|
if (authDefault != null) return authDefault;
|
||||||
for (AuthProviderPair pair : auth.values()) {
|
for (AuthProviderPair pair : auth.values()) {
|
||||||
|
@ -70,17 +135,6 @@ public AuthProviderPair getAuthProviderPair() {
|
||||||
throw new IllegalStateException("Default AuthProviderPair not found");
|
throw new IllegalStateException("Default AuthProviderPair not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Component> components;
|
|
||||||
|
|
||||||
public ExeConf launch4j;
|
|
||||||
public NettyConfig netty;
|
|
||||||
|
|
||||||
public LauncherConf launcher;
|
|
||||||
public CertificateConf certificate;
|
|
||||||
public JarSignerConf sign;
|
|
||||||
|
|
||||||
public String startScript;
|
|
||||||
|
|
||||||
public void setProjectName(String projectName) {
|
public void setProjectName(String projectName) {
|
||||||
this.projectName = projectName;
|
this.projectName = projectName;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +147,6 @@ public void setEnv(LauncherConfig.LauncherEnvironment env) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void verify() {
|
public void verify() {
|
||||||
if (auth == null || auth.size() < 1) {
|
if (auth == null || auth.size() < 1) {
|
||||||
throw new NullPointerException("AuthProviderPair`s count should be at least one");
|
throw new NullPointerException("AuthProviderPair`s count should be at least one");
|
||||||
|
@ -122,7 +175,7 @@ public void verify() {
|
||||||
|
|
||||||
public void init(LaunchServer.ReloadType type) {
|
public void init(LaunchServer.ReloadType type) {
|
||||||
Launcher.applyLauncherEnv(env);
|
Launcher.applyLauncherEnv(env);
|
||||||
for (Map.Entry<String,AuthProviderPair> provider : auth.entrySet()) {
|
for (Map.Entry<String, AuthProviderPair> provider : auth.entrySet()) {
|
||||||
provider.getValue().init(server, provider.getKey());
|
provider.getValue().init(server, provider.getKey());
|
||||||
}
|
}
|
||||||
if (dao != null) {
|
if (dao != null) {
|
||||||
|
@ -170,10 +223,9 @@ public void close(LaunchServer.ReloadType type) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogHelper.error(e);
|
LogHelper.error(e);
|
||||||
}
|
}
|
||||||
if(dao != null) {
|
if (dao != null) {
|
||||||
server.unregisterObject("dao", dao);
|
server.unregisterObject("dao", dao);
|
||||||
if(dao instanceof AutoCloseable)
|
if (dao instanceof AutoCloseable) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
((AutoCloseable) dao).close();
|
((AutoCloseable) dao).close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -264,73 +316,4 @@ public NettyBindAddress(String address, int port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
|
||||||
LaunchServerConfig newConfig = new LaunchServerConfig();
|
|
||||||
newConfig.mirrors = new String[]{"https://mirror.gravit.pro/"};
|
|
||||||
newConfig.launch4j = new LaunchServerConfig.ExeConf();
|
|
||||||
newConfig.launch4j.enabled = true;
|
|
||||||
newConfig.launch4j.copyright = "© GravitLauncher Team";
|
|
||||||
newConfig.launch4j.fileDesc = "GravitLauncher ".concat(Version.getVersion().getVersionString());
|
|
||||||
newConfig.launch4j.fileVer = Version.getVersion().getVersionString().concat(".").concat(String.valueOf(Version.getVersion().patch));
|
|
||||||
newConfig.launch4j.internalName = "Launcher";
|
|
||||||
newConfig.launch4j.trademarks = "This product is licensed under GPLv3";
|
|
||||||
newConfig.launch4j.txtFileVersion = "%s, build %d";
|
|
||||||
newConfig.launch4j.txtProductVersion = "%s, build %d";
|
|
||||||
newConfig.launch4j.productName = "GravitLauncher";
|
|
||||||
newConfig.launch4j.productVer = newConfig.launch4j.fileVer;
|
|
||||||
newConfig.launch4j.maxVersion = "1.8.999";
|
|
||||||
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
|
|
||||||
newConfig.startScript = JVMHelper.OS_TYPE.equals(JVMHelper.OS.MUSTDIE) ? "." + File.separator + "start.bat" : "." + File.separator + "start.sh";
|
|
||||||
newConfig.auth = new HashMap<>();
|
|
||||||
AuthProviderPair a = new AuthProviderPair(new RejectAuthProvider("Настройте authProvider"),
|
|
||||||
new MemoryAuthHandler(),
|
|
||||||
new RequestTextureProvider("http://example.com/skins/%username%.png", "http://example.com/cloaks/%username%.png")
|
|
||||||
);
|
|
||||||
a.displayName = "Default";
|
|
||||||
newConfig.auth.put("std", a);
|
|
||||||
newConfig.protectHandler = new StdProtectHandler();
|
|
||||||
newConfig.binaryName = "Launcher";
|
|
||||||
|
|
||||||
newConfig.netty = new NettyConfig();
|
|
||||||
newConfig.netty.fileServerEnabled = true;
|
|
||||||
newConfig.netty.binds = new NettyBindAddress[]{new NettyBindAddress("0.0.0.0", 9274)};
|
|
||||||
newConfig.netty.performance = new NettyPerformanceConfig();
|
|
||||||
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.launcher = new LauncherConf();
|
|
||||||
newConfig.launcher.guardType = "no";
|
|
||||||
newConfig.launcher.compress = true;
|
|
||||||
newConfig.launcher.warningMissArchJava = true;
|
|
||||||
newConfig.launcher.attachLibraryBeforeProGuard = false;
|
|
||||||
newConfig.launcher.deleteTempFiles = true;
|
|
||||||
newConfig.launcher.enabledProGuard = true;
|
|
||||||
newConfig.launcher.stripLineNumbers = true;
|
|
||||||
newConfig.launcher.proguardGenMappings = true;
|
|
||||||
|
|
||||||
newConfig.certificate = new LaunchServerConfig.CertificateConf();
|
|
||||||
newConfig.certificate.enabled = false;
|
|
||||||
newConfig.sign = new JarSignerConf();
|
|
||||||
|
|
||||||
newConfig.components = new HashMap<>();
|
|
||||||
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
|
|
||||||
authLimiterComponent.rateLimit = 3;
|
|
||||||
authLimiterComponent.rateLimitMillis = 8000;
|
|
||||||
authLimiterComponent.message = "Превышен лимит авторизаций";
|
|
||||||
newConfig.components.put("authLimiter", authLimiterComponent);
|
|
||||||
RegLimiterComponent regLimiterComponent = new RegLimiterComponent();
|
|
||||||
regLimiterComponent.rateLimit = 3;
|
|
||||||
regLimiterComponent.rateLimitMillis = 1000 * 60 * 60 * 10; //Блок на 10 часов
|
|
||||||
regLimiterComponent.message = "Превышен лимит регистраций";
|
|
||||||
newConfig.components.put("regLimiter", regLimiterComponent);
|
|
||||||
newConfig.netty.sendExceptionEnabled = true;
|
|
||||||
return newConfig;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,24 @@
|
||||||
|
|
||||||
public interface User {
|
public interface User {
|
||||||
String getUsername();
|
String getUsername();
|
||||||
|
|
||||||
ClientPermissions getPermissions();
|
ClientPermissions getPermissions();
|
||||||
|
|
||||||
void setPermissions(ClientPermissions permissions);
|
void setPermissions(ClientPermissions permissions);
|
||||||
|
|
||||||
boolean verifyPassword(String password);
|
boolean verifyPassword(String password);
|
||||||
|
|
||||||
void setPassword(String password);
|
void setPassword(String password);
|
||||||
|
|
||||||
String getAccessToken();
|
String getAccessToken();
|
||||||
|
|
||||||
void setAccessToken(String accessToken);
|
void setAccessToken(String accessToken);
|
||||||
|
|
||||||
String getServerID();
|
String getServerID();
|
||||||
|
|
||||||
void setServerID(String serverID);
|
void setServerID(String serverID);
|
||||||
|
|
||||||
UUID getUuid();
|
UUID getUuid();
|
||||||
|
|
||||||
void setUuid(UUID uuid);
|
void setUuid(UUID uuid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,21 @@
|
||||||
@Entity(name = "User")
|
@Entity(name = "User")
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
public class UserHibernateImpl implements User {
|
public class UserHibernateImpl implements User {
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private long id;
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
public String username;
|
public String username;
|
||||||
public String email;
|
public String email;
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
public UUID uuid;
|
public UUID uuid;
|
||||||
|
public String serverID;
|
||||||
|
public long permissions;
|
||||||
|
public long flags;
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
@Column(name = "password")
|
@Column(name = "password")
|
||||||
private byte[] password;
|
private byte[] password;
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
public String serverID;
|
|
||||||
private String password_salt;
|
private String password_salt;
|
||||||
public long permissions;
|
|
||||||
public long flags;
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
public void setPassword(String password) {
|
||||||
password_salt = SecurityHelper.randomStringAESKey();
|
password_salt = SecurityHelper.randomStringAESKey();
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.Reconfigurable;
|
import pro.gravit.launchserver.Reconfigurable;
|
||||||
import pro.gravit.launchserver.dao.User;
|
import pro.gravit.launchserver.dao.User;
|
||||||
import pro.gravit.launchserver.dao.impl.UserHibernateImpl;
|
|
||||||
import pro.gravit.launchserver.dao.impl.HibernateUserDAOImpl;
|
import pro.gravit.launchserver.dao.impl.HibernateUserDAOImpl;
|
||||||
|
import pro.gravit.launchserver.dao.impl.UserHibernateImpl;
|
||||||
import pro.gravit.utils.command.Command;
|
import pro.gravit.utils.command.Command;
|
||||||
import pro.gravit.utils.command.SubCommand;
|
import pro.gravit.utils.command.SubCommand;
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
|
|
|
@ -27,12 +27,94 @@
|
||||||
|
|
||||||
public class SignHelper {
|
public class SignHelper {
|
||||||
|
|
||||||
|
public static final OutputStream NULL = new OutputStream() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NullOutputStream";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte array. */
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte array. */
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte. */
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Never closes */
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final String hashFunctionName = "SHA-256";
|
||||||
|
|
||||||
|
private SignHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the KeyStore with given algo.
|
||||||
|
*/
|
||||||
|
public static KeyStore getStore(Path file, String storepass, String algo) throws IOException {
|
||||||
|
try {
|
||||||
|
KeyStore st = KeyStore.getInstance(algo);
|
||||||
|
st.load(IOHelper.newInput(file), storepass != null ? storepass.toCharArray() : null);
|
||||||
|
return st;
|
||||||
|
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the beast that can actually sign the data (for JKS, for other make it).
|
||||||
|
*/
|
||||||
|
public static CMSSignedDataGenerator createSignedDataGenerator(KeyStore keyStore, String keyAlias, String signAlgo, String keyPassword) throws KeyStoreException, OperatorCreationException, CertificateEncodingException, UnrecoverableKeyException, NoSuchAlgorithmException, CMSException {
|
||||||
|
List<Certificate> certChain = new ArrayList<>(Arrays.asList(keyStore.getCertificateChain(keyAlias)));
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Store certStore = new JcaCertStore(certChain);
|
||||||
|
Certificate cert = keyStore.getCertificate(keyAlias);
|
||||||
|
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword != null ? keyPassword.toCharArray() : null);
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder(signAlgo).setProvider("BC").build(privateKey);
|
||||||
|
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||||
|
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
||||||
|
SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
|
||||||
|
generator.addSignerInfoGenerator(sig);
|
||||||
|
generator.addCertificates(certStore);
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CMSSignedDataGenerator createSignedDataGenerator(PrivateKey privateKey, Certificate cert, List<Certificate> certChain, String signAlgo) throws OperatorCreationException, CertificateEncodingException, CMSException {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Store certStore = new JcaCertStore(certChain);
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder(signAlgo).setProvider("BC").build(privateKey);
|
||||||
|
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||||
|
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
||||||
|
SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
|
||||||
|
generator.addSignerInfoGenerator(sig);
|
||||||
|
generator.addCertificates(certStore);
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageDigest hasher() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance(hashFunctionName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper output stream that also sends the data to the given.
|
* Helper output stream that also sends the data to the given.
|
||||||
*/
|
*/
|
||||||
public static class HashingOutputStream extends OutputStream {
|
public static class HashingOutputStream extends OutputStream {
|
||||||
public final OutputStream out;
|
public final OutputStream out;
|
||||||
public final MessageDigest hasher;
|
public final MessageDigest hasher;
|
||||||
|
|
||||||
public HashingOutputStream(OutputStream out, MessageDigest hasher) {
|
public HashingOutputStream(OutputStream out, MessageDigest hasher) {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
|
@ -68,9 +150,10 @@ public void write(int b) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] digest() {
|
public byte[] digest() {
|
||||||
return hasher.digest();
|
return hasher.digest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper output stream that also sends the data to the given.
|
* Helper output stream that also sends the data to the given.
|
||||||
*/
|
*/
|
||||||
|
@ -81,88 +164,7 @@ public HashingNonClosingOutputStream(OutputStream out, MessageDigest hasher) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final OutputStream NULL = new OutputStream() {
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "NullOutputStream";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Discards the specified byte array. */
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Discards the specified byte array. */
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Discards the specified byte. */
|
|
||||||
@Override
|
|
||||||
public void write(int b) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Never closes */
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private SignHelper() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the KeyStore with given algo.
|
|
||||||
*/
|
|
||||||
public static KeyStore getStore(Path file, String storepass, String algo) throws IOException {
|
|
||||||
try {
|
|
||||||
KeyStore st = KeyStore.getInstance(algo);
|
|
||||||
st.load(IOHelper.newInput(file), storepass != null ? storepass.toCharArray() : null);
|
|
||||||
return st;
|
|
||||||
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the beast that can actually sign the data (for JKS, for other make it).
|
|
||||||
*/
|
|
||||||
public static CMSSignedDataGenerator createSignedDataGenerator(KeyStore keyStore, String keyAlias, String signAlgo, String keyPassword) throws KeyStoreException, OperatorCreationException, CertificateEncodingException, UnrecoverableKeyException, NoSuchAlgorithmException, CMSException {
|
|
||||||
List<Certificate> certChain = new ArrayList<>(Arrays.asList(keyStore.getCertificateChain(keyAlias)));
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Store certStore = new JcaCertStore(certChain);
|
|
||||||
Certificate cert = keyStore.getCertificate(keyAlias);
|
|
||||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword != null ? keyPassword.toCharArray() : null);
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder(signAlgo).setProvider("BC").build(privateKey);
|
|
||||||
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
|
||||||
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
|
||||||
SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
|
|
||||||
generator.addSignerInfoGenerator(sig);
|
|
||||||
generator.addCertificates(certStore);
|
|
||||||
return generator;
|
|
||||||
}
|
|
||||||
public static CMSSignedDataGenerator createSignedDataGenerator(PrivateKey privateKey, Certificate cert, List<Certificate> certChain, String signAlgo) throws OperatorCreationException, CertificateEncodingException, CMSException {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Store certStore = new JcaCertStore(certChain);
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder(signAlgo).setProvider("BC").build(privateKey);
|
|
||||||
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
|
||||||
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
|
||||||
SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
|
|
||||||
generator.addSignerInfoGenerator(sig);
|
|
||||||
generator.addCertificates(certStore);
|
|
||||||
return generator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String hashFunctionName = "SHA-256";
|
|
||||||
|
|
||||||
public static MessageDigest hasher() {
|
|
||||||
try {
|
|
||||||
return MessageDigest.getInstance(hashFunctionName);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package pro.gravit.launchserver.launchermodules;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
public class LauncherModuleClassLoader extends URLClassLoader {
|
||||||
|
public LauncherModuleClassLoader(ClassLoader parent) {
|
||||||
|
super(new URL[0], parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addURL(URL u) {
|
||||||
|
super.addURL(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> rawDefineClass(String name, byte[] bytes) {
|
||||||
|
return defineClass(name, bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package pro.gravit.launchserver.launchermodules;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.Launcher;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
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.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
public class LauncherModuleLoader {
|
||||||
|
public final List<ModuleEntity> launcherModules = new ArrayList<>();
|
||||||
|
public final Path modulesDir;
|
||||||
|
private final LaunchServer server;
|
||||||
|
|
||||||
|
public LauncherModuleLoader(LaunchServer server) {
|
||||||
|
this.server = server; modulesDir = server.dir.resolve("launcher-modules");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if (!IOHelper.isDir(modulesDir)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(modulesDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.commandHandler.registerCommand("syncLauncherModules", new SyncLauncherModulesCommand(this));
|
||||||
|
MainBuildTask mainTask = server.launcherBinary.getTaskByClass(MainBuildTask.class).get();
|
||||||
|
mainTask.preBuildHook.registerHook((buildContext) -> {
|
||||||
|
for (ModuleEntity e : launcherModules) {
|
||||||
|
if (e.propertyMap != null) buildContext.task.properties.putAll(e.propertyMap);
|
||||||
|
buildContext.clientModules.add(e.moduleMainClass);
|
||||||
|
buildContext.readerClassPath.add(new JarFile(e.path.toFile()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mainTask.postBuildHook.registerHook((buildContext) -> {
|
||||||
|
for (ModuleEntity e : launcherModules) {
|
||||||
|
LogHelper.debug("Put %s launcher module", e.path.toString());
|
||||||
|
buildContext.pushJarFile(e.path, (en) -> false, (en) -> true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
syncModules();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncModules() throws IOException {
|
||||||
|
launcherModules.clear();
|
||||||
|
IOHelper.walk(modulesDir, new ModulesVisitor(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ModuleEntity {
|
||||||
|
public Path path;
|
||||||
|
public String moduleMainClass;
|
||||||
|
public String moduleConfigClass;
|
||||||
|
public String moduleConfigName;
|
||||||
|
public Map<String, Object> propertyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final class ModulesVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private LauncherModuleClassLoader classLoader;
|
||||||
|
|
||||||
|
private ModulesVisitor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (file.toFile().getName().endsWith(".jar"))
|
||||||
|
try (JarFile f = new JarFile(file.toFile())) {
|
||||||
|
Attributes attributes = f.getManifest().getMainAttributes();
|
||||||
|
String mainClass = attributes.getValue("Module-Main-Class");
|
||||||
|
if (mainClass == null) {
|
||||||
|
LogHelper.error("In module %s MainClass not found", file.toString());
|
||||||
|
} else {
|
||||||
|
ModuleEntity entity = new ModuleEntity();
|
||||||
|
entity.path = file;
|
||||||
|
entity.moduleMainClass = mainClass;
|
||||||
|
entity.moduleConfigClass = attributes.getValue("Module-Config-Class");
|
||||||
|
if (entity.moduleConfigClass != null) {
|
||||||
|
entity.moduleConfigName = attributes.getValue("Module-Config-Name");
|
||||||
|
if (entity.moduleConfigName == null) {
|
||||||
|
LogHelper.warning("Module-Config-Name in module %s null. Module not configured", file.toString());
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (classLoader == null)
|
||||||
|
classLoader = new LauncherModuleClassLoader(server.modulesManager.getModuleClassLoader());
|
||||||
|
classLoader.addURL(file.toUri().toURL());
|
||||||
|
Class<?> clazz = classLoader.loadClass(entity.moduleConfigClass);
|
||||||
|
Path configPath = server.modulesManager.getConfigManager().getModuleConfig(entity.moduleConfigName);
|
||||||
|
Object defaultConfig = MethodHandles.publicLookup().findStatic(clazz, "getDefault", MethodType.methodType(Object.class)).invoke();
|
||||||
|
Object targetConfig;
|
||||||
|
if (!Files.exists(configPath)) {
|
||||||
|
LogHelper.debug("Write default config for module %s to %s", file.toString(), configPath.toString());
|
||||||
|
try (Writer writer = IOHelper.newWriter(configPath)) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(defaultConfig, writer);
|
||||||
|
}
|
||||||
|
targetConfig = defaultConfig;
|
||||||
|
} else {
|
||||||
|
try (Reader reader = IOHelper.newReader(configPath)) {
|
||||||
|
targetConfig = Launcher.gsonManager.configGson.fromJson(reader, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Field[] fields = clazz.getFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
if ((field.getModifiers() & Modifier.STATIC) != 0) continue;
|
||||||
|
Object obj = field.get(targetConfig);
|
||||||
|
String configPropertyName = "modules.".concat(entity.moduleConfigName.toLowerCase()).concat(".").concat(field.getName().toLowerCase());
|
||||||
|
if (entity.propertyMap == null) entity.propertyMap = new HashMap<>();
|
||||||
|
LogHelper.dev("Property name %s", configPropertyName);
|
||||||
|
entity.propertyMap.put(configPropertyName, obj);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launcherModules.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package pro.gravit.launchserver.launchermodules;
|
||||||
|
|
||||||
|
import pro.gravit.utils.command.Command;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
public class SyncLauncherModulesCommand extends Command {
|
||||||
|
private final LauncherModuleLoader mod;
|
||||||
|
|
||||||
|
public SyncLauncherModulesCommand(LauncherModuleLoader mod) {
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getArgsDescription() {
|
||||||
|
return "Resync launcher modules";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsageDescription() {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(String... args) throws Exception {
|
||||||
|
mod.syncModules();
|
||||||
|
LogHelper.info("Launcher Modules synced");
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,11 +20,11 @@
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.bouncycastle.util.io.pem.PemWriter;
|
import org.bouncycastle.util.io.pem.PemWriter;
|
||||||
|
import pro.gravit.launcher.LauncherTrustManager;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
import pro.gravit.utils.helper.JVMHelper;
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
import pro.gravit.launcher.LauncherTrustManager;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -48,17 +48,13 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CertificateManager {
|
public class CertificateManager {
|
||||||
public X509CertificateHolder ca;
|
|
||||||
public AsymmetricKeyParameter caKey;
|
|
||||||
|
|
||||||
public X509CertificateHolder server;
|
|
||||||
public AsymmetricKeyParameter serverKey;
|
|
||||||
|
|
||||||
public LauncherTrustManager trustManager;
|
|
||||||
|
|
||||||
public final int validDays = 60;
|
public final int validDays = 60;
|
||||||
public final int minusHours = 6;
|
public final int minusHours = 6;
|
||||||
|
public X509CertificateHolder ca;
|
||||||
|
public AsymmetricKeyParameter caKey;
|
||||||
|
public X509CertificateHolder server;
|
||||||
|
public AsymmetricKeyParameter serverKey;
|
||||||
|
public LauncherTrustManager trustManager;
|
||||||
public String orgName;
|
public String orgName;
|
||||||
|
|
||||||
public X509CertificateHolder generateCertificate(String subjectName, PublicKey subjectPublicKey) throws OperatorCreationException {
|
public X509CertificateHolder generateCertificate(String subjectName, PublicKey subjectPublicKey) throws OperatorCreationException {
|
||||||
|
|
|
@ -14,26 +14,6 @@
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class MirrorManager {
|
public class MirrorManager {
|
||||||
public static class Mirror {
|
|
||||||
final String baseUrl;
|
|
||||||
boolean enabled;
|
|
||||||
|
|
||||||
Mirror(String url) {
|
|
||||||
//assetsURLMask = url.concat("assets/%s.zip");
|
|
||||||
//clientsURLMask = url.concat("clients/%s.zip");
|
|
||||||
baseUrl = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL formatArgs(String mask, Object... args) throws MalformedURLException {
|
|
||||||
Object[] data = Arrays.stream(args).map(e -> IOHelper.urlEncode(e.toString())).toArray();
|
|
||||||
return new URL(baseUrl.concat(String.format(mask, data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public URL getURL(String mask, Object... args) throws MalformedURLException {
|
|
||||||
return formatArgs(mask, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final ArrayList<Mirror> list = new ArrayList<>();
|
protected final ArrayList<Mirror> list = new ArrayList<>();
|
||||||
private Mirror defaultMirror;
|
private Mirror defaultMirror;
|
||||||
|
|
||||||
|
@ -118,4 +98,24 @@ public JsonElement jsonRequest(JsonElement request, String method, String mask,
|
||||||
}
|
}
|
||||||
throw new IOException("Error jsonRequest. All mirrors return error");
|
throw new IOException("Error jsonRequest. All mirrors return error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Mirror {
|
||||||
|
final String baseUrl;
|
||||||
|
boolean enabled;
|
||||||
|
|
||||||
|
Mirror(String url) {
|
||||||
|
//assetsURLMask = url.concat("assets/%s.zip");
|
||||||
|
//clientsURLMask = url.concat("clients/%s.zip");
|
||||||
|
baseUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL formatArgs(String mask, Object... args) throws MalformedURLException {
|
||||||
|
Object[] data = Arrays.stream(args).map(e -> IOHelper.urlEncode(e.toString())).toArray();
|
||||||
|
return new URL(baseUrl.concat(String.format(mask, data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getURL(String mask, Object... args) throws MalformedURLException {
|
||||||
|
return formatArgs(mask, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,27 +10,6 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ReconfigurableManager {
|
public class ReconfigurableManager {
|
||||||
private static class ReconfigurableVirtualCommand extends Command {
|
|
||||||
public ReconfigurableVirtualCommand(Map<String, Command> childs) {
|
|
||||||
super(childs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getArgsDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsageDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) throws Exception {
|
|
||||||
invokeSubcommands(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HashMap<String, Command> RECONFIGURABLE = new HashMap<>();
|
private final HashMap<String, Command> RECONFIGURABLE = new HashMap<>();
|
||||||
|
|
||||||
public void registerReconfigurable(String name, Reconfigurable reconfigurable) {
|
public void registerReconfigurable(String name, Reconfigurable reconfigurable) {
|
||||||
|
@ -59,4 +38,25 @@ public void printHelp(String name) throws CommandException {
|
||||||
public Map<String, Command> getCommands() {
|
public Map<String, Command> getCommands() {
|
||||||
return RECONFIGURABLE;
|
return RECONFIGURABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ReconfigurableVirtualCommand extends Command {
|
||||||
|
public ReconfigurableVirtualCommand(Map<String, Command> childs) {
|
||||||
|
super(childs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getArgsDescription() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsageDescription() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(String... args) throws Exception {
|
||||||
|
invokeSubcommands(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,7 @@ public Client getOrNewClient(long session) {
|
||||||
return clientSet.computeIfAbsent(session, Client::new);
|
return clientSet.computeIfAbsent(session, Client::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Client removeClient(long session)
|
public Client removeClient(long session) {
|
||||||
{
|
|
||||||
return clientSet.remove(session);
|
return clientSet.remove(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ public class AuthHookManager {
|
||||||
public final BiHookSet<CheckServerResponse, Client> checkServerHook = new BiHookSet<>();
|
public final BiHookSet<CheckServerResponse, Client> checkServerHook = new BiHookSet<>();
|
||||||
public final BiHookSet<JoinServerResponse, Client> joinServerHook = new BiHookSet<>();
|
public final BiHookSet<JoinServerResponse, Client> joinServerHook = new BiHookSet<>();
|
||||||
public final BiHookSet<SetProfileResponse, Client> setProfileHook = new BiHookSet<>();
|
public final BiHookSet<SetProfileResponse, Client> setProfileHook = new BiHookSet<>();
|
||||||
|
public final HookSet<RegContext> registraion = new HookSet<>();
|
||||||
|
|
||||||
public static class RegContext {
|
public static class RegContext {
|
||||||
public final String login;
|
public final String login;
|
||||||
|
@ -28,6 +29,4 @@ public RegContext(String login, String password, String ip, boolean trustContext
|
||||||
this.trustContext = trustContext;
|
this.trustContext = trustContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final HookSet<RegContext> registraion = new HookSet<>();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package pro.gravit.launchserver.modules.impl;
|
package pro.gravit.launchserver.modules.impl;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.LauncherTrustManager;
|
||||||
import pro.gravit.launcher.modules.LauncherModule;
|
import pro.gravit.launcher.modules.LauncherModule;
|
||||||
import pro.gravit.launcher.modules.LauncherModuleInfo;
|
import pro.gravit.launcher.modules.LauncherModuleInfo;
|
||||||
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
|
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
import pro.gravit.launcher.LauncherTrustManager;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
|
@ -47,8 +47,8 @@ public enum Type {
|
||||||
SERVER,
|
SERVER,
|
||||||
USER
|
USER
|
||||||
}
|
}
|
||||||
public static class TrustLevel
|
|
||||||
{
|
public static class TrustLevel {
|
||||||
public byte[] verifySecureKey;
|
public byte[] verifySecureKey;
|
||||||
public boolean keyChecked;
|
public boolean keyChecked;
|
||||||
public byte[] publicKey;
|
public byte[] publicKey;
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
public class LauncherNettyServer implements AutoCloseable {
|
public class LauncherNettyServer implements AutoCloseable {
|
||||||
|
private static final String WEBSOCKET_PATH = "/api";
|
||||||
public final ServerBootstrap serverBootstrap;
|
public final ServerBootstrap serverBootstrap;
|
||||||
public final EventLoopGroup bossGroup;
|
public final EventLoopGroup bossGroup;
|
||||||
public final EventLoopGroup workerGroup;
|
public final EventLoopGroup workerGroup;
|
||||||
public final WebSocketService service;
|
public final WebSocketService service;
|
||||||
public final BiHookSet<NettyConnectContext, SocketChannel> pipelineHook = new BiHookSet<>();
|
public final BiHookSet<NettyConnectContext, SocketChannel> pipelineHook = new BiHookSet<>();
|
||||||
private static final String WEBSOCKET_PATH = "/api";
|
|
||||||
|
|
||||||
public LauncherNettyServer(LaunchServer server) {
|
public LauncherNettyServer(LaunchServer server) {
|
||||||
LaunchServerConfig.NettyConfig config = server.config.netty;
|
LaunchServerConfig.NettyConfig config = server.config.netty;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
import pro.gravit.launchserver.socket.response.profile.ProfileByUUIDResponse;
|
import pro.gravit.launchserver.socket.response.profile.ProfileByUUIDResponse;
|
||||||
import pro.gravit.launchserver.socket.response.profile.ProfileByUsername;
|
import pro.gravit.launchserver.socket.response.profile.ProfileByUsername;
|
||||||
import pro.gravit.launchserver.socket.response.secure.GetSecureLevelInfoResponse;
|
import pro.gravit.launchserver.socket.response.secure.GetSecureLevelInfoResponse;
|
||||||
|
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
|
||||||
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
||||||
import pro.gravit.launchserver.socket.response.secure.VerifySecureLevelKeyResponse;
|
import pro.gravit.launchserver.socket.response.secure.VerifySecureLevelKeyResponse;
|
||||||
import pro.gravit.launchserver.socket.response.update.LauncherResponse;
|
import pro.gravit.launchserver.socket.response.update.LauncherResponse;
|
||||||
|
@ -35,22 +36,11 @@
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
public class WebSocketService {
|
public class WebSocketService {
|
||||||
public final ChannelGroup channels;
|
|
||||||
public static final ProviderMap<WebSocketServerResponse> providers = new ProviderMap<>();
|
public static final ProviderMap<WebSocketServerResponse> providers = new ProviderMap<>();
|
||||||
|
public final ChannelGroup channels;
|
||||||
public static class WebSocketRequestContext {
|
|
||||||
public final WebSocketServerResponse response;
|
|
||||||
public final Client client;
|
|
||||||
public final String ip;
|
|
||||||
|
|
||||||
public WebSocketRequestContext(WebSocketServerResponse response, Client client, String ip) {
|
|
||||||
this.response = response;
|
|
||||||
this.client = client;
|
|
||||||
this.ip = ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final BiHookSet<WebSocketRequestContext, ChannelHandlerContext> hook = new BiHookSet<>();
|
public final BiHookSet<WebSocketRequestContext, ChannelHandlerContext> hook = new BiHookSet<>();
|
||||||
|
private final LaunchServer server;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
public WebSocketService(ChannelGroup channels, LaunchServer server) {
|
public WebSocketService(ChannelGroup channels, LaunchServer server) {
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
|
@ -61,15 +51,36 @@ public WebSocketService(ChannelGroup channels, LaunchServer server) {
|
||||||
this.gson = Launcher.gsonManager.gson;
|
this.gson = Launcher.gsonManager.gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LaunchServer server;
|
public static void registerResponses() {
|
||||||
private final Gson gson;
|
providers.register("auth", AuthResponse.class);
|
||||||
|
providers.register("checkServer", CheckServerResponse.class);
|
||||||
|
providers.register("joinServer", JoinServerResponse.class);
|
||||||
|
providers.register("profiles", ProfilesResponse.class);
|
||||||
|
providers.register("launcher", LauncherResponse.class);
|
||||||
|
providers.register("updateList", UpdateListResponse.class);
|
||||||
|
providers.register("cmdExec", ExecCommandResponse.class);
|
||||||
|
providers.register("setProfile", SetProfileResponse.class);
|
||||||
|
providers.register("addLogListener", AddLogListenerResponse.class);
|
||||||
|
providers.register("update", UpdateResponse.class);
|
||||||
|
providers.register("restoreSession", RestoreSessionResponse.class);
|
||||||
|
providers.register("batchProfileByUsername", BatchProfileByUsername.class);
|
||||||
|
providers.register("profileByUsername", ProfileByUsername.class);
|
||||||
|
providers.register("profileByUUID", ProfileByUUIDResponse.class);
|
||||||
|
providers.register("getAvailabilityAuth", GetAvailabilityAuthResponse.class);
|
||||||
|
providers.register("register", RegisterResponse.class);
|
||||||
|
providers.register("setPassword", SetPasswordResponse.class);
|
||||||
|
providers.register("exit", ExitResponse.class);
|
||||||
|
providers.register("getSecureLevelInfo", GetSecureLevelInfoResponse.class);
|
||||||
|
providers.register("verifySecureLevelKey", VerifySecureLevelKeyResponse.class);
|
||||||
|
providers.register("securityReport", SecurityReportResponse.class);
|
||||||
|
providers.register("hardwareReport", HardwareReportResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
public void process(ChannelHandlerContext ctx, TextWebSocketFrame frame, Client client, String ip) {
|
public void process(ChannelHandlerContext ctx, TextWebSocketFrame frame, Client client, String ip) {
|
||||||
String request = frame.text();
|
String request = frame.text();
|
||||||
WebSocketServerResponse response = gson.fromJson(request, WebSocketServerResponse.class);
|
WebSocketServerResponse response = gson.fromJson(request, WebSocketServerResponse.class);
|
||||||
if(response == null)
|
if (response == null) {
|
||||||
{
|
RequestEvent event = new ErrorRequestEvent("This type of request is not supported");
|
||||||
RequestEvent event= new ErrorRequestEvent("This type of request is not supported");
|
|
||||||
sendObject(ctx, event);
|
sendObject(ctx, event);
|
||||||
}
|
}
|
||||||
process(ctx, response, client, ip);
|
process(ctx, response, client, ip);
|
||||||
|
@ -109,30 +120,6 @@ public void registerClient(Channel channel) {
|
||||||
channels.add(channel);
|
channels.add(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerResponses() {
|
|
||||||
providers.register("auth", AuthResponse.class);
|
|
||||||
providers.register("checkServer", CheckServerResponse.class);
|
|
||||||
providers.register("joinServer", JoinServerResponse.class);
|
|
||||||
providers.register("profiles", ProfilesResponse.class);
|
|
||||||
providers.register("launcher", LauncherResponse.class);
|
|
||||||
providers.register("updateList", UpdateListResponse.class);
|
|
||||||
providers.register("cmdExec", ExecCommandResponse.class);
|
|
||||||
providers.register("setProfile", SetProfileResponse.class);
|
|
||||||
providers.register("addLogListener", AddLogListenerResponse.class);
|
|
||||||
providers.register("update", UpdateResponse.class);
|
|
||||||
providers.register("restoreSession", RestoreSessionResponse.class);
|
|
||||||
providers.register("batchProfileByUsername", BatchProfileByUsername.class);
|
|
||||||
providers.register("profileByUsername", ProfileByUsername.class);
|
|
||||||
providers.register("profileByUUID", ProfileByUUIDResponse.class);
|
|
||||||
providers.register("getAvailabilityAuth", GetAvailabilityAuthResponse.class);
|
|
||||||
providers.register("register", RegisterResponse.class);
|
|
||||||
providers.register("setPassword", SetPasswordResponse.class);
|
|
||||||
providers.register("exit", ExitResponse.class);
|
|
||||||
providers.register("getSecureLevelInfo", GetSecureLevelInfoResponse.class);
|
|
||||||
providers.register("verifySecureLevelKey", VerifySecureLevelKeyResponse.class);
|
|
||||||
providers.register("securityReport", SecurityReportResponse.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendObject(ChannelHandlerContext ctx, Object obj) {
|
public void sendObject(ChannelHandlerContext ctx, Object obj) {
|
||||||
ctx.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj, WebSocketEvent.class)), ctx.voidPromise());
|
ctx.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj, WebSocketEvent.class)), ctx.voidPromise());
|
||||||
}
|
}
|
||||||
|
@ -173,6 +160,18 @@ public void sendEvent(EventResult obj) {
|
||||||
channels.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj)), ChannelMatchers.all(), true);
|
channels.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj)), ChannelMatchers.all(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class WebSocketRequestContext {
|
||||||
|
public final WebSocketServerResponse response;
|
||||||
|
public final Client client;
|
||||||
|
public final String ip;
|
||||||
|
|
||||||
|
public WebSocketRequestContext(WebSocketServerResponse response, Client client, String ip) {
|
||||||
|
this.response = response;
|
||||||
|
this.client = client;
|
||||||
|
this.ip = ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class EventResult implements WebSocketEvent {
|
public static class EventResult implements WebSocketEvent {
|
||||||
public EventResult() {
|
public EventResult() {
|
||||||
|
|
||||||
|
|
|
@ -4,29 +4,30 @@
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
public enum ContentType {
|
public enum ContentType {
|
||||||
NONE {
|
NONE {
|
||||||
@Override
|
@Override
|
||||||
public String forPath(File p) {
|
public String forPath(File p) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
NIO {
|
NIO {
|
||||||
@Override
|
@Override
|
||||||
public String forPath(File p) {
|
public String forPath(File p) {
|
||||||
try {
|
try {
|
||||||
return Files.probeContentType(p.toPath());
|
return Files.probeContentType(p.toPath());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
return UNIVERSAL.forPath(p);
|
return UNIVERSAL.forPath(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UNIVERSAL {
|
UNIVERSAL {
|
||||||
@Override
|
@Override
|
||||||
public String forPath(File p) {
|
public String forPath(File p) {
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
public abstract String forPath(File p);
|
|
||||||
|
public abstract String forPath(File p);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,15 @@
|
||||||
|
|
||||||
import javax.net.ssl.SSLServerSocketFactory;
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
// TODO refactor
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
|
public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
|
||||||
private SSLServerSocketFactory ssf;
|
private transient final LaunchServer server;
|
||||||
|
|
||||||
public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections");
|
public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections");
|
||||||
|
|
||||||
public LauncherNettyServer nettyServer;
|
public LauncherNettyServer nettyServer;
|
||||||
|
private SSLServerSocketFactory ssf;
|
||||||
// API
|
|
||||||
private Set<Socket> sockets;
|
|
||||||
|
|
||||||
private transient final LaunchServer server;
|
|
||||||
|
|
||||||
public NettyServerSocketHandler(LaunchServer server) {
|
public NettyServerSocketHandler(LaunchServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
@ -29,7 +23,9 @@ public NettyServerSocketHandler(LaunchServer server) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
//TODO: Close Impl
|
if (nettyServer == null) return;
|
||||||
|
nettyServer.close();
|
||||||
|
nettyServer.service.channels.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,9 +15,14 @@
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
||||||
|
static {
|
||||||
|
}
|
||||||
|
|
||||||
public final LaunchServer srv;
|
public final LaunchServer srv;
|
||||||
public final WebSocketService service;
|
public final WebSocketService service;
|
||||||
|
private final UUID connectUUID = UUID.randomUUID();
|
||||||
public NettyConnectContext context;
|
public NettyConnectContext context;
|
||||||
|
private Client client;
|
||||||
|
|
||||||
public WebSocketFrameHandler(NettyConnectContext context, LaunchServer srv, WebSocketService service) {
|
public WebSocketFrameHandler(NettyConnectContext context, LaunchServer srv, WebSocketService service) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -25,20 +30,14 @@ public WebSocketFrameHandler(NettyConnectContext context, LaunchServer srv, WebS
|
||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Client client;
|
public Client getClient() {
|
||||||
private final UUID connectUUID = UUID.randomUUID();
|
return client;
|
||||||
|
|
||||||
static {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClient(Client client) {
|
public void setClient(Client client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Client getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final UUID getConnectUUID() {
|
public final UUID getConnectUUID() {
|
||||||
return connectUUID;
|
return connectUUID;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,16 @@ public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpReque
|
||||||
|
|
||||||
public static final SimpleDateFormat dateFormatter;
|
public static final SimpleDateFormat dateFormatter;
|
||||||
public static final String READ = "r";
|
public static final String READ = "r";
|
||||||
|
public static final int HTTP_CACHE_SECONDS = VerifyHelper.verifyInt(Integer.parseInt(System.getProperty("launcher.fileserver.cachesec", "60")), VerifyHelper.NOT_NEGATIVE, "HttpCache seconds should be positive");
|
||||||
|
private static final boolean OLD_ALGO = Boolean.parseBoolean(System.getProperty("launcher.fileserver.oldalgo", "true"));
|
||||||
|
private static final ContentType TYPE_PROBE = Arrays.stream(ContentType.values()).filter(e -> e.name().toLowerCase(Locale.US).equals(System.getProperty("launcher.fileserver.typeprobe", "nio"))).findFirst().orElse(ContentType.UNIVERSAL);
|
||||||
|
private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[^-\\._]?[^<>&\\\"]*");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||||
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
}
|
}
|
||||||
public static final int HTTP_CACHE_SECONDS = VerifyHelper.verifyInt(Integer.parseInt(System.getProperty("launcher.fileserver.cachesec", "60")), VerifyHelper.NOT_NEGATIVE, "HttpCache seconds should be positive");
|
|
||||||
private static final boolean OLD_ALGO = Boolean.parseBoolean(System.getProperty("launcher.fileserver.oldalgo", "true"));
|
|
||||||
private static final ContentType TYPE_PROBE = Arrays.stream(ContentType.values()).filter(e -> e.name().toLowerCase(Locale.US).equals(System.getProperty("launcher.fileserver.typeprobe", "nio"))).findFirst().orElse(ContentType.UNIVERSAL);
|
|
||||||
private final Path base;
|
private final Path base;
|
||||||
private final boolean fullOut;
|
private final boolean fullOut;
|
||||||
private final boolean showHiddenFiles;
|
private final boolean showHiddenFiles;
|
||||||
|
@ -45,6 +48,120 @@ public FileServerHandler(Path base, boolean fullOut, boolean showHiddenFiles) {
|
||||||
this.showHiddenFiles = showHiddenFiles;
|
this.showHiddenFiles = showHiddenFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void sendListing(ChannelHandlerContext ctx, File dir, String dirPath, boolean showHidden) {
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
|
||||||
|
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder()
|
||||||
|
.append("<!DOCTYPE html>\r\n")
|
||||||
|
.append("<html><head><meta charset='utf-8' /><title>")
|
||||||
|
.append("Listing of: ")
|
||||||
|
.append(dirPath)
|
||||||
|
.append("</title></head><body>\r\n")
|
||||||
|
|
||||||
|
.append("<h3>Listing of: ")
|
||||||
|
.append(dirPath)
|
||||||
|
.append("</h3>\r\n")
|
||||||
|
|
||||||
|
.append("<ul>")
|
||||||
|
.append("<li><a href=\"../\">..</a></li>\r\n");
|
||||||
|
|
||||||
|
for (File f : dir.listFiles()) {
|
||||||
|
if ((f.isHidden() && !showHidden) || !f.canRead()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = f.getName();
|
||||||
|
if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.append("<li><a href=\"")
|
||||||
|
.append(name)
|
||||||
|
.append("\">")
|
||||||
|
.append(name)
|
||||||
|
.append("</a></li>\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.append("</ul></body></html>\r\n");
|
||||||
|
ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
|
||||||
|
response.content().writeBytes(buffer);
|
||||||
|
buffer.release();
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
|
||||||
|
response.headers().set(HttpHeaderNames.LOCATION, newUri);
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||||
|
HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
|
||||||
|
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
|
||||||
|
*
|
||||||
|
* @param ctx Context
|
||||||
|
*/
|
||||||
|
private static void sendNotModified(ChannelHandlerContext ctx) {
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);
|
||||||
|
setDateHeader(response);
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Date header for the HTTP response
|
||||||
|
*
|
||||||
|
* @param response HTTP response
|
||||||
|
*/
|
||||||
|
private static void setDateHeader(FullHttpResponse response) {
|
||||||
|
response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(new Date(System.currentTimeMillis())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Date and Cache headers for the HTTP Response
|
||||||
|
*
|
||||||
|
* @param response HTTP response
|
||||||
|
* @param fileToCache file to extract content type
|
||||||
|
*/
|
||||||
|
private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
|
||||||
|
// Date header
|
||||||
|
Calendar time = new GregorianCalendar();
|
||||||
|
response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime()));
|
||||||
|
|
||||||
|
// Add cache headers
|
||||||
|
time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
|
||||||
|
response.headers().set(HttpHeaderNames.EXPIRES, dateFormatter.format(time.getTime()));
|
||||||
|
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
|
||||||
|
response.headers().set(
|
||||||
|
HttpHeaderNames.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the content type header for the HTTP Response
|
||||||
|
*
|
||||||
|
* @param response HTTP response
|
||||||
|
* @param file file to extract content type
|
||||||
|
*/
|
||||||
|
private static void setContentTypeHeader(HttpResponse response, File file) {
|
||||||
|
String contentType = TYPE_PROBE.forPath(file);
|
||||||
|
if (contentType != null)
|
||||||
|
response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||||
if (!request.decoderResult().isSuccess()) {
|
if (!request.decoderResult().isSuccess()) {
|
||||||
|
@ -154,120 +271,4 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
sendError(ctx, INTERNAL_SERVER_ERROR);
|
sendError(ctx, INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[^-\\._]?[^<>&\\\"]*");
|
|
||||||
|
|
||||||
private static void sendListing(ChannelHandlerContext ctx, File dir, String dirPath, boolean showHidden) {
|
|
||||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
|
|
||||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
|
|
||||||
|
|
||||||
StringBuilder buf = new StringBuilder()
|
|
||||||
.append("<!DOCTYPE html>\r\n")
|
|
||||||
.append("<html><head><meta charset='utf-8' /><title>")
|
|
||||||
.append("Listing of: ")
|
|
||||||
.append(dirPath)
|
|
||||||
.append("</title></head><body>\r\n")
|
|
||||||
|
|
||||||
.append("<h3>Listing of: ")
|
|
||||||
.append(dirPath)
|
|
||||||
.append("</h3>\r\n")
|
|
||||||
|
|
||||||
.append("<ul>")
|
|
||||||
.append("<li><a href=\"../\">..</a></li>\r\n");
|
|
||||||
|
|
||||||
for (File f : dir.listFiles()) {
|
|
||||||
if ((f.isHidden() && !showHidden) || !f.canRead()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = f.getName();
|
|
||||||
if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.append("<li><a href=\"")
|
|
||||||
.append(name)
|
|
||||||
.append("\">")
|
|
||||||
.append(name)
|
|
||||||
.append("</a></li>\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.append("</ul></body></html>\r\n");
|
|
||||||
ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
|
|
||||||
response.content().writeBytes(buffer);
|
|
||||||
buffer.release();
|
|
||||||
|
|
||||||
// Close the connection as soon as the error message is sent.
|
|
||||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
|
|
||||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
|
|
||||||
response.headers().set(HttpHeaderNames.LOCATION, newUri);
|
|
||||||
|
|
||||||
// Close the connection as soon as the error message is sent.
|
|
||||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
|
|
||||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
|
||||||
HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
|
|
||||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
|
|
||||||
|
|
||||||
// Close the connection as soon as the error message is sent.
|
|
||||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
|
|
||||||
*
|
|
||||||
* @param ctx Context
|
|
||||||
*/
|
|
||||||
private static void sendNotModified(ChannelHandlerContext ctx) {
|
|
||||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);
|
|
||||||
setDateHeader(response);
|
|
||||||
|
|
||||||
// Close the connection as soon as the error message is sent.
|
|
||||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Date header for the HTTP response
|
|
||||||
*
|
|
||||||
* @param response HTTP response
|
|
||||||
*/
|
|
||||||
private static void setDateHeader(FullHttpResponse response) {
|
|
||||||
response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(new Date(System.currentTimeMillis())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Date and Cache headers for the HTTP Response
|
|
||||||
*
|
|
||||||
* @param response HTTP response
|
|
||||||
* @param fileToCache file to extract content type
|
|
||||||
*/
|
|
||||||
private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
|
|
||||||
// Date header
|
|
||||||
Calendar time = new GregorianCalendar();
|
|
||||||
response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime()));
|
|
||||||
|
|
||||||
// Add cache headers
|
|
||||||
time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
|
|
||||||
response.headers().set(HttpHeaderNames.EXPIRES, dateFormatter.format(time.getTime()));
|
|
||||||
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
|
|
||||||
response.headers().set(
|
|
||||||
HttpHeaderNames.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the content type header for the HTTP Response
|
|
||||||
*
|
|
||||||
* @param response HTTP response
|
|
||||||
* @param file file to extract content type
|
|
||||||
*/
|
|
||||||
private static void setContentTypeHeader(HttpResponse response, File file) {
|
|
||||||
String contentType = TYPE_PROBE.forPath(file);
|
|
||||||
if (contentType != null)
|
|
||||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import pro.gravit.launcher.events.request.AuthRequestEvent;
|
import pro.gravit.launcher.events.request.AuthRequestEvent;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
import pro.gravit.launcher.request.auth.password.AuthECPassword;
|
import pro.gravit.launcher.request.auth.password.AuthECPassword;
|
||||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -37,13 +35,6 @@ public class AuthResponse extends SimpleResponse {
|
||||||
public String auth_id;
|
public String auth_id;
|
||||||
public ConnectTypes authType;
|
public ConnectTypes authType;
|
||||||
|
|
||||||
public enum ConnectTypes {
|
|
||||||
@Deprecated
|
|
||||||
SERVER,
|
|
||||||
CLIENT,
|
|
||||||
API
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "auth";
|
return "auth";
|
||||||
|
@ -68,8 +59,7 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
||||||
AuthProviderPair pair;
|
AuthProviderPair pair;
|
||||||
if (auth_id.isEmpty()) pair = server.config.getAuthProviderPair();
|
if (auth_id.isEmpty()) pair = server.config.getAuthProviderPair();
|
||||||
else pair = server.config.getAuthProviderPair(auth_id);
|
else pair = server.config.getAuthProviderPair(auth_id);
|
||||||
if(pair == null)
|
if (pair == null) {
|
||||||
{
|
|
||||||
sendError("auth_id incorrect");
|
sendError("auth_id incorrect");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +107,22 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ConnectTypes {
|
||||||
|
@Deprecated
|
||||||
|
SERVER,
|
||||||
|
CLIENT,
|
||||||
|
API
|
||||||
|
}
|
||||||
|
|
||||||
public static class AuthContext {
|
public static class AuthContext {
|
||||||
|
public final String login;
|
||||||
|
public final String profileName;
|
||||||
|
public final String ip;
|
||||||
|
public final ConnectTypes authType;
|
||||||
|
public final Client client;
|
||||||
|
@Deprecated
|
||||||
|
public int password_length; //Use AuthProvider for get password
|
||||||
|
|
||||||
public AuthContext(Client client, String login, String profileName, String ip, ConnectTypes authType) {
|
public AuthContext(Client client, String login, String profileName, String ip, ConnectTypes authType) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.login = login;
|
this.login = login;
|
||||||
|
@ -125,13 +130,5 @@ public AuthContext(Client client, String login, String profileName, String ip, C
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
this.authType = authType;
|
this.authType = authType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final String login;
|
|
||||||
@Deprecated
|
|
||||||
public int password_length; //Use AuthProvider for get password
|
|
||||||
public final String profileName;
|
|
||||||
public final String ip;
|
|
||||||
public final ConnectTypes authType;
|
|
||||||
public final Client client;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,7 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client pClient) {
|
public void execute(ChannelHandlerContext ctx, Client pClient) {
|
||||||
if(!pClient.isAuth || pClient.type == AuthResponse.ConnectTypes.CLIENT)
|
if (!pClient.isAuth || pClient.type == AuthResponse.ConnectTypes.CLIENT) {
|
||||||
{
|
|
||||||
sendError("Permissions denied");
|
sendError("Permissions denied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
public class ExitResponse extends SimpleResponse {
|
public class ExitResponse extends SimpleResponse {
|
||||||
public boolean exitAll;
|
public boolean exitAll;
|
||||||
public String username;
|
public String username;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "exit";
|
return "exit";
|
||||||
|
@ -18,66 +19,56 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
if(username != null && ( !client.isAuth || client.permissions == null || !client.permissions.isPermission(ClientPermissions.PermissionConsts.ADMIN) ))
|
if (username != null && (!client.isAuth || client.permissions == null || !client.permissions.isPermission(ClientPermissions.PermissionConsts.ADMIN))) {
|
||||||
{
|
|
||||||
sendError("Permissions denied");
|
sendError("Permissions denied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(username == null)
|
if (username == null) {
|
||||||
{
|
if (client.session == 0 && exitAll) {
|
||||||
if(client.session == 0 && exitAll)
|
|
||||||
{
|
|
||||||
sendError("Session invalid");
|
sendError("Session invalid");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WebSocketFrameHandler handler = ctx.pipeline().get(WebSocketFrameHandler.class);
|
WebSocketFrameHandler handler = ctx.pipeline().get(WebSocketFrameHandler.class);
|
||||||
if(handler == null)
|
if (handler == null) {
|
||||||
{
|
|
||||||
sendError("Exit internal error");
|
sendError("Exit internal error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Client newClient = new Client(0);
|
Client newClient = new Client(0);
|
||||||
newClient.checkSign = client.checkSign;
|
newClient.checkSign = client.checkSign;
|
||||||
handler.setClient(newClient);
|
handler.setClient(newClient);
|
||||||
if(client.session != 0) server.sessionManager.removeClient(client.session);
|
if (client.session != 0) server.sessionManager.removeClient(client.session);
|
||||||
if(exitAll)
|
if (exitAll) {
|
||||||
{
|
|
||||||
service.channels.forEach((channel) -> {
|
service.channels.forEach((channel) -> {
|
||||||
if(channel == null || channel.pipeline() == null) return;
|
if (channel == null || channel.pipeline() == null) return;
|
||||||
WebSocketFrameHandler wsHandler = channel.pipeline().get(WebSocketFrameHandler.class);
|
WebSocketFrameHandler wsHandler = channel.pipeline().get(WebSocketFrameHandler.class);
|
||||||
if(wsHandler == null || wsHandler == handler) return;
|
if (wsHandler == null || wsHandler == handler) return;
|
||||||
Client chClient = wsHandler.getClient();
|
Client chClient = wsHandler.getClient();
|
||||||
if(client.isAuth && client.username != null)
|
if (client.isAuth && client.username != null) {
|
||||||
{
|
if (!chClient.isAuth || !client.username.equals(chClient.username)) return;
|
||||||
if(!chClient.isAuth || !client.username.equals(chClient.username)) return;
|
} else {
|
||||||
}
|
if (chClient.session != client.session) return;
|
||||||
else
|
|
||||||
{
|
|
||||||
if(chClient.session != client.session) return;
|
|
||||||
}
|
}
|
||||||
Client newCusClient = new Client(0);
|
Client newCusClient = new Client(0);
|
||||||
newCusClient.checkSign = chClient.checkSign;
|
newCusClient.checkSign = chClient.checkSign;
|
||||||
wsHandler.setClient(newCusClient);
|
wsHandler.setClient(newCusClient);
|
||||||
if(chClient.session != 0) server.sessionManager.removeClient(chClient.session);
|
if (chClient.session != 0) server.sessionManager.removeClient(chClient.session);
|
||||||
ExitRequestEvent event = new ExitRequestEvent(ExitRequestEvent.ExitReason.SERVER);
|
ExitRequestEvent event = new ExitRequestEvent(ExitRequestEvent.ExitReason.SERVER);
|
||||||
event.requestUUID = RequestEvent.eventUUID;
|
event.requestUUID = RequestEvent.eventUUID;
|
||||||
wsHandler.service.sendObject(channel, event);
|
wsHandler.service.sendObject(channel, event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sendResult(new ExitRequestEvent(ExitRequestEvent.ExitReason.CLIENT));
|
sendResult(new ExitRequestEvent(ExitRequestEvent.ExitReason.CLIENT));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
service.channels.forEach((channel -> {
|
service.channels.forEach((channel -> {
|
||||||
if(channel == null || channel.pipeline() == null) return;
|
if (channel == null || channel.pipeline() == null) return;
|
||||||
WebSocketFrameHandler wsHandler = channel.pipeline().get(WebSocketFrameHandler.class);
|
WebSocketFrameHandler wsHandler = channel.pipeline().get(WebSocketFrameHandler.class);
|
||||||
if(wsHandler == null) return;
|
if (wsHandler == null) return;
|
||||||
Client chClient = wsHandler.getClient();
|
Client chClient = wsHandler.getClient();
|
||||||
if(!chClient.isAuth || !username.equals(chClient.username)) return;
|
if (!chClient.isAuth || !username.equals(chClient.username)) return;
|
||||||
Client newCusClient = new Client(0);
|
Client newCusClient = new Client(0);
|
||||||
newCusClient.checkSign = chClient.checkSign;
|
newCusClient.checkSign = chClient.checkSign;
|
||||||
wsHandler.setClient(newCusClient);
|
wsHandler.setClient(newCusClient);
|
||||||
if(chClient.session != 0) server.sessionManager.removeClient(chClient.session);
|
if (chClient.session != 0) server.sessionManager.removeClient(chClient.session);
|
||||||
ExitRequestEvent event = new ExitRequestEvent(ExitRequestEvent.ExitReason.SERVER);
|
ExitRequestEvent event = new ExitRequestEvent(ExitRequestEvent.ExitReason.SERVER);
|
||||||
event.requestUUID = RequestEvent.eventUUID;
|
event.requestUUID = RequestEvent.eventUUID;
|
||||||
wsHandler.service.sendObject(channel, event);
|
wsHandler.service.sendObject(channel, event);
|
||||||
|
|
|
@ -20,8 +20,7 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) {
|
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||||
if(!client.isAuth || client.type != AuthResponse.ConnectTypes.CLIENT)
|
if (!client.isAuth || client.type != AuthResponse.ConnectTypes.CLIENT) {
|
||||||
{
|
|
||||||
sendError("Permissions denied");
|
sendError("Permissions denied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package pro.gravit.launchserver.socket.response.auth;
|
package pro.gravit.launchserver.socket.response.auth;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import pro.gravit.launcher.events.request.ErrorRequestEvent;
|
|
||||||
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
|
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
|
@ -26,20 +25,15 @@ public void execute(ChannelHandlerContext ctx, Client client) {
|
||||||
|
|
||||||
List<ClientProfile> profileList;
|
List<ClientProfile> profileList;
|
||||||
List<ClientProfile> serverProfiles = server.getProfiles();
|
List<ClientProfile> serverProfiles = server.getProfiles();
|
||||||
if (server.config.protectHandler instanceof ProfilesProtectHandler)
|
if (server.config.protectHandler instanceof ProfilesProtectHandler) {
|
||||||
{
|
|
||||||
ProfilesProtectHandler protectHandler = (ProfilesProtectHandler) server.config.protectHandler;
|
ProfilesProtectHandler protectHandler = (ProfilesProtectHandler) server.config.protectHandler;
|
||||||
profileList = new ArrayList<>(4);
|
profileList = new ArrayList<>(4);
|
||||||
for(ClientProfile profile : serverProfiles)
|
for (ClientProfile profile : serverProfiles) {
|
||||||
{
|
if (protectHandler.canGetProfile(profile, client)) {
|
||||||
if(protectHandler.canGetProfile(profile, client))
|
|
||||||
{
|
|
||||||
profileList.add(profile);
|
profileList.add(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
profileList = serverProfiles;
|
profileList = serverProfiles;
|
||||||
}
|
}
|
||||||
sendResult(new ProfilesRequestEvent(profileList));
|
sendResult(new ProfilesRequestEvent(profileList));
|
||||||
|
|
|
@ -19,6 +19,12 @@ public class RegisterResponse extends SimpleResponse {
|
||||||
public String email;
|
public String email;
|
||||||
public byte[] verifyHash;
|
public byte[] verifyHash;
|
||||||
|
|
||||||
|
public static byte[] registerHash(String login, String secret) throws NoSuchAlgorithmException {
|
||||||
|
String text = login.concat("+").concat(secret);
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
return digest.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
byte[] normalHash = registerHash(login, server.runtime.registerApiKey);
|
byte[] normalHash = registerHash(login, server.runtime.registerApiKey);
|
||||||
|
@ -43,10 +49,4 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "register";
|
return "register";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] registerHash(String login, String secret) throws NoSuchAlgorithmException {
|
|
||||||
String text = login.concat("+").concat(secret);
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
return digest.digest(text.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,6 @@
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BatchProfileByUsername extends SimpleResponse {
|
public class BatchProfileByUsername extends SimpleResponse {
|
||||||
static class Entry {
|
|
||||||
String username;
|
|
||||||
String client;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entry[] list;
|
Entry[] list;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,4 +31,9 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
}
|
}
|
||||||
sendResult(result);
|
sendResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Entry {
|
||||||
|
String username;
|
||||||
|
String client;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,21 +14,20 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
if(!(server.config.protectHandler instanceof SecureProtectHandler))
|
if (!(server.config.protectHandler instanceof SecureProtectHandler)) {
|
||||||
{
|
|
||||||
GetSecureLevelInfoRequestEvent response = new GetSecureLevelInfoRequestEvent(null);
|
GetSecureLevelInfoRequestEvent response = new GetSecureLevelInfoRequestEvent(null);
|
||||||
response.enabled = false;
|
response.enabled = false;
|
||||||
sendResult(response);
|
sendResult(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
||||||
if(!secureProtectHandler.allowGetSecureLevelInfo(client))
|
if (!secureProtectHandler.allowGetSecureLevelInfo(client)) {
|
||||||
{
|
|
||||||
sendError("Access denied");
|
sendError("Access denied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
|
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
|
||||||
if(client.trustLevel.verifySecureKey == null) client.trustLevel.verifySecureKey = secureProtectHandler.generateSecureLevelKey();
|
if (client.trustLevel.verifySecureKey == null)
|
||||||
|
client.trustLevel.verifySecureKey = secureProtectHandler.generateSecureLevelKey();
|
||||||
GetSecureLevelInfoRequestEvent response = new GetSecureLevelInfoRequestEvent(client.trustLevel.verifySecureKey);
|
GetSecureLevelInfoRequestEvent response = new GetSecureLevelInfoRequestEvent(client.trustLevel.verifySecureKey);
|
||||||
response.enabled = true;
|
response.enabled = true;
|
||||||
sendResult(secureProtectHandler.onGetSecureLevelInfo(response));
|
sendResult(secureProtectHandler.onGetSecureLevelInfo(response));
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package pro.gravit.launchserver.socket.response.secure;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||||
|
|
||||||
|
public class HardwareReportResponse extends SimpleResponse {
|
||||||
|
public HardwareReportRequest.HardwareInfo hardware;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return "hardwareReport";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ public class SecurityReportResponse extends SimpleResponse {
|
||||||
public String largeData;
|
public String largeData;
|
||||||
public byte[] smallBytes;
|
public byte[] smallBytes;
|
||||||
public byte[] largeBytes;
|
public byte[] largeBytes;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "securityReport";
|
return "securityReport";
|
||||||
|
@ -20,8 +21,7 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
if(!(server.config.protectHandler instanceof SecureProtectHandler))
|
if (!(server.config.protectHandler instanceof SecureProtectHandler)) {
|
||||||
{
|
|
||||||
sendError("Method not allowed");
|
sendError("Method not allowed");
|
||||||
}
|
}
|
||||||
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
public class VerifySecureLevelKeyResponse extends SimpleResponse {
|
public class VerifySecureLevelKeyResponse extends SimpleResponse {
|
||||||
public byte[] publicKey;
|
public byte[] publicKey;
|
||||||
public byte[] signature;
|
public byte[] signature;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return "verifySecureLevelKey";
|
return "verifySecureLevelKey";
|
||||||
|
@ -19,24 +20,20 @@ public String getType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
|
||||||
if(!(server.config.protectHandler instanceof SecureProtectHandler) || client.trustLevel == null || client.trustLevel.verifySecureKey == null)
|
if (!(server.config.protectHandler instanceof SecureProtectHandler) || client.trustLevel == null || client.trustLevel.verifySecureKey == null) {
|
||||||
{
|
|
||||||
sendError("This method not allowed");
|
sendError("This method not allowed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
|
||||||
try {
|
try {
|
||||||
secureProtectHandler.verifySecureLevelKey(publicKey, client.trustLevel.verifySecureKey, signature);
|
secureProtectHandler.verifySecureLevelKey(publicKey, client.trustLevel.verifySecureKey, signature);
|
||||||
} catch (InvalidKeySpecException e)
|
} catch (InvalidKeySpecException e) {
|
||||||
{
|
|
||||||
sendError("Invalid public key");
|
sendError("Invalid public key");
|
||||||
return;
|
return;
|
||||||
} catch (SignatureException e)
|
} catch (SignatureException e) {
|
||||||
{
|
|
||||||
sendError("Invalid signature");
|
sendError("Invalid signature");
|
||||||
return;
|
return;
|
||||||
} catch (SecurityException e)
|
} catch (SecurityException e) {
|
||||||
{
|
|
||||||
sendError(e.getMessage());
|
sendError(e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,6 @@ private boolean checkSecure(String hash, String salt) {
|
||||||
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
byte[] normal_hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
||||||
server.runtime.clientCheckSecret.concat(".").concat(salt));
|
server.runtime.clientCheckSecret.concat(".").concat(salt));
|
||||||
byte[] launcher_hash = Base64.getDecoder().decode(hash);
|
byte[] launcher_hash = Base64.getDecoder().decode(hash);
|
||||||
//LogHelper.debug("[checkSecure] %s vs %s", Arrays.toString(normal_hash), Arrays.toString(launcher_hash));
|
|
||||||
return Arrays.equals(normal_hash, launcher_hash);
|
return Arrays.equals(normal_hash, launcher_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,10 @@
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import pro.gravit.launcher.events.request.UpdateRequestEvent;
|
import pro.gravit.launcher.events.request.UpdateRequestEvent;
|
||||||
import pro.gravit.launcher.hasher.HashedDir;
|
import pro.gravit.launcher.hasher.HashedDir;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
public class UpdateResponse extends SimpleResponse {
|
public class UpdateResponse extends SimpleResponse {
|
||||||
|
|
|
@ -12,40 +12,21 @@
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ASMTransformersTest {
|
public class ASMTransformersTest {
|
||||||
public static class ASMClassLoader extends ClassLoader
|
|
||||||
{
|
|
||||||
public ASMClassLoader(ClassLoader parent) {
|
|
||||||
super(parent);
|
|
||||||
}
|
|
||||||
public void rawDefineClass(String name, byte[] bytes, int offset, int length)
|
|
||||||
{
|
|
||||||
defineClass(name, bytes, offset, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static ASMClassLoader classLoader;
|
public static ASMClassLoader classLoader;
|
||||||
public static class TestClass
|
|
||||||
{
|
|
||||||
@LauncherInject(value = "testprop")
|
|
||||||
public int test;
|
|
||||||
@LauncherInject(value = "testprop2")
|
|
||||||
public List<String> s;
|
|
||||||
@LauncherInject(value = "testprop3")
|
|
||||||
public Map<String, String> map;
|
|
||||||
}
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void prepare() throws Throwable {
|
public static void prepare() throws Throwable {
|
||||||
classLoader = new ASMClassLoader(ASMTransformersTest.class.getClassLoader());
|
classLoader = new ASMClassLoader(ASMTransformersTest.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testASM() throws Throwable
|
void testASM() throws Throwable {
|
||||||
{
|
|
||||||
ClassReader reader = new ClassReader(JarHelper.getClassBytes(TestClass.class));
|
ClassReader reader = new ClassReader(JarHelper.getClassBytes(TestClass.class));
|
||||||
ClassNode node = new ClassNode();
|
ClassNode node = new ClassNode();
|
||||||
reader.accept(node, ClassReader.SKIP_DEBUG);
|
reader.accept(node, ClassReader.SKIP_DEBUG);
|
||||||
|
@ -69,11 +50,30 @@ void testASM() throws Throwable
|
||||||
Class<?> clazz = classLoader.loadClass("ASMTestClass");
|
Class<?> clazz = classLoader.loadClass("ASMTestClass");
|
||||||
Object instance = MethodHandles.publicLookup().findConstructor(clazz, MethodType.methodType(void.class)).invoke();
|
Object instance = MethodHandles.publicLookup().findConstructor(clazz, MethodType.methodType(void.class)).invoke();
|
||||||
Assertions.assertEquals(1234, (int)
|
Assertions.assertEquals(1234, (int)
|
||||||
MethodHandles.publicLookup().findGetter(clazz, "test", int.class).invoke(instance));
|
MethodHandles.publicLookup().findGetter(clazz, "test", int.class).invoke(instance));
|
||||||
Assertions.assertEquals(strings, (List<String>)
|
Assertions.assertEquals(strings, (List<String>)
|
||||||
MethodHandles.publicLookup().findGetter(clazz, "s", List.class).invoke(instance));
|
MethodHandles.publicLookup().findGetter(clazz, "s", List.class).invoke(instance));
|
||||||
|
|
||||||
Assertions.assertEquals(byteMap, (Map<String, Object>)
|
Assertions.assertEquals(byteMap, (Map<String, Object>)
|
||||||
MethodHandles.publicLookup().findGetter(clazz, "map", Map.class).invoke(instance));
|
MethodHandles.publicLookup().findGetter(clazz, "map", Map.class).invoke(instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ASMClassLoader extends ClassLoader {
|
||||||
|
public ASMClassLoader(ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rawDefineClass(String name, byte[] bytes, int offset, int length) {
|
||||||
|
defineClass(name, bytes, offset, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
@LauncherInject(value = "testprop")
|
||||||
|
public int test;
|
||||||
|
@LauncherInject(value = "testprop2")
|
||||||
|
public List<String> s;
|
||||||
|
@LauncherInject(value = "testprop3")
|
||||||
|
public Map<String, String> map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package pro.gravit.launchserver;
|
package pro.gravit.launchserver;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
@ -28,7 +29,7 @@ public class StartLaunchServerTest {
|
||||||
public static LaunchServer launchServer;
|
public static LaunchServer launchServer;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void prepare() throws Exception {
|
public static void prepare() throws Throwable {
|
||||||
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(modulesDir, configDir, null);
|
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(modulesDir, configDir, null);
|
||||||
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
||||||
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
|
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
|
||||||
|
@ -48,12 +49,14 @@ public static void prepare() throws Exception {
|
||||||
.setLaunchServerConfigManager(new LaunchServer.LaunchServerConfigManager() {
|
.setLaunchServerConfigManager(new LaunchServer.LaunchServerConfigManager() {
|
||||||
@Override
|
@Override
|
||||||
public LaunchServerConfig readConfig() {
|
public LaunchServerConfig readConfig() {
|
||||||
return null;
|
return LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
||||||
return null;
|
LaunchServerRuntimeConfig r = new LaunchServerRuntimeConfig();
|
||||||
|
r.reset();
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -71,6 +74,11 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
||||||
launchServer = builder.build();
|
launchServer = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void complete() throws Throwable {
|
||||||
|
launchServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start() {
|
public void start() {
|
||||||
launchServer.run();
|
launchServer.run();
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
}
|
}
|
||||||
javafx {
|
javafx {
|
||||||
version = "12"
|
version = "12"
|
||||||
modules = [ 'javafx.controls', 'javafx.fxml' ]
|
modules = ['javafx.controls', 'javafx.fxml']
|
||||||
}
|
}
|
||||||
sourceCompatibility = '1.8'
|
sourceCompatibility = '1.8'
|
||||||
targetCompatibility = '1.8'
|
targetCompatibility = '1.8'
|
||||||
|
|
|
@ -19,8 +19,8 @@ public class ClientLauncherWrapper {
|
||||||
public static final String MAGIC_ARG = "-Djdk.attach.allowAttachSelf";
|
public static final String MAGIC_ARG = "-Djdk.attach.allowAttachSelf";
|
||||||
public static final String WAIT_PROCESS_PROPERTY = "launcher.waitProcess";
|
public static final String WAIT_PROCESS_PROPERTY = "launcher.waitProcess";
|
||||||
public static final String NO_JAVA9_CHECK_PROPERTY = "launcher.noJava9Check";
|
public static final String NO_JAVA9_CHECK_PROPERTY = "launcher.noJava9Check";
|
||||||
public static boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY);
|
|
||||||
public static final boolean noJava9check = Boolean.getBoolean(NO_JAVA9_CHECK_PROPERTY);
|
public static final boolean noJava9check = Boolean.getBoolean(NO_JAVA9_CHECK_PROPERTY);
|
||||||
|
public static boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY);
|
||||||
|
|
||||||
public static void main(String[] arguments) throws IOException, InterruptedException {
|
public static void main(String[] arguments) throws IOException, InterruptedException {
|
||||||
LogHelper.printVersion("Launcher");
|
LogHelper.printVersion("Launcher");
|
||||||
|
@ -76,7 +76,7 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
|
||||||
if (!modulePath.isEmpty()) {
|
if (!modulePath.isEmpty()) {
|
||||||
args.add("--add-modules");
|
args.add("--add-modules");
|
||||||
String javaModules = "javafx.base,javafx.fxml,javafx.controls,jdk.unsupported";
|
String javaModules = "javafx.base,javafx.fxml,javafx.controls,jdk.unsupported";
|
||||||
if(useSwing) javaModules = javaModules.concat(",javafx.swing");
|
if (useSwing) javaModules = javaModules.concat(",javafx.swing");
|
||||||
args.add(javaModules);
|
args.add(javaModules);
|
||||||
args.add("--module-path");
|
args.add("--module-path");
|
||||||
args.add(modulePath);
|
args.add(modulePath);
|
||||||
|
|
|
@ -8,11 +8,11 @@ public abstract class JSApplication extends Application {
|
||||||
private static final AtomicReference<JSApplication> INSTANCE = new AtomicReference<>();
|
private static final AtomicReference<JSApplication> INSTANCE = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
||||||
public static JSApplication getInstance() {
|
|
||||||
return INSTANCE.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSApplication() {
|
public JSApplication() {
|
||||||
INSTANCE.set(this);
|
INSTANCE.set(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JSApplication getInstance() {
|
||||||
|
return INSTANCE.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
|
|
||||||
|
|
||||||
public final class LauncherAgent {
|
public final class LauncherAgent {
|
||||||
private static boolean isAgentStarted = false;
|
|
||||||
public static Instrumentation inst;
|
public static Instrumentation inst;
|
||||||
|
private static boolean isAgentStarted = false;
|
||||||
|
|
||||||
public static void addJVMClassPath(String path) throws IOException {
|
public static void addJVMClassPath(String path) throws IOException {
|
||||||
LogHelper.debug("Launcher Agent addJVMClassPath");
|
LogHelper.debug("Launcher Agent addJVMClassPath");
|
||||||
|
@ -25,10 +25,6 @@ public static void addJVMClassPath(Path path) throws IOException {
|
||||||
inst.appendToSystemClassLoaderSearch(new JarFile(path.toFile()));
|
inst.appendToSystemClassLoaderSearch(new JarFile(path.toFile()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAgentStarted() {
|
|
||||||
return isAgentStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void premain(String agentArgument, Instrumentation instrumentation) {
|
public static void premain(String agentArgument, Instrumentation instrumentation) {
|
||||||
System.out.println("Launcher Agent");
|
System.out.println("Launcher Agent");
|
||||||
checkAgentStacktrace();
|
checkAgentStacktrace();
|
||||||
|
@ -58,4 +54,8 @@ public static void checkAgentStacktrace() {
|
||||||
public static boolean isStarted() {
|
public static boolean isStarted() {
|
||||||
return isAgentStarted;
|
return isAgentStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAgentStarted() {
|
||||||
|
return isAgentStarted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package pro.gravit.launcher;
|
package pro.gravit.launcher;
|
||||||
|
|
||||||
import pro.gravit.launcher.client.*;
|
import pro.gravit.launcher.client.ClientLauncherEntryPoint;
|
||||||
|
import pro.gravit.launcher.client.ClientLauncherProcess;
|
||||||
|
import pro.gravit.launcher.client.ClientModuleManager;
|
||||||
|
import pro.gravit.launcher.client.DirBridge;
|
||||||
import pro.gravit.launcher.client.events.ClientEngineInitPhase;
|
import pro.gravit.launcher.client.events.ClientEngineInitPhase;
|
||||||
import pro.gravit.launcher.client.events.ClientExitPhase;
|
import pro.gravit.launcher.client.events.ClientExitPhase;
|
||||||
import pro.gravit.launcher.client.events.ClientPreGuiPhase;
|
import pro.gravit.launcher.client.events.ClientPreGuiPhase;
|
||||||
|
@ -33,6 +36,20 @@
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class LauncherEngine {
|
public class LauncherEngine {
|
||||||
|
public static final AtomicBoolean IS_CLIENT = new AtomicBoolean(false);
|
||||||
|
public static ClientLauncherProcess.ClientParams clientParams;
|
||||||
|
public static LauncherGuardInterface guard;
|
||||||
|
public static ClientModuleManager modulesManager;
|
||||||
|
// Instance
|
||||||
|
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||||
|
public RuntimeProvider runtimeProvider;
|
||||||
|
public ECPublicKey publicKey;
|
||||||
|
public ECPrivateKey privateKey;
|
||||||
|
|
||||||
|
private LauncherEngine() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//JVMHelper.getCertificates
|
//JVMHelper.getCertificates
|
||||||
public static X509Certificate[] getCertificates(Class<?> clazz) {
|
public static X509Certificate[] getCertificates(Class<?> clazz) {
|
||||||
Object[] signers = clazz.getSigners();
|
Object[] signers = clazz.getSigners();
|
||||||
|
@ -40,10 +57,6 @@ public static X509Certificate[] getCertificates(Class<?> clazz) {
|
||||||
return Arrays.stream(signers).filter((c) -> c instanceof X509Certificate).map((c) -> (X509Certificate) c).toArray(X509Certificate[]::new);
|
return Arrays.stream(signers).filter((c) -> c instanceof X509Certificate).map((c) -> (X509Certificate) c).toArray(X509Certificate[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final AtomicBoolean IS_CLIENT = new AtomicBoolean(false);
|
|
||||||
public static ClientLauncherProcess.ClientParams clientParams;
|
|
||||||
public static LauncherGuardInterface guard;
|
|
||||||
|
|
||||||
public static void checkClass(Class<?> clazz) throws SecurityException {
|
public static void checkClass(Class<?> clazz) throws SecurityException {
|
||||||
LauncherTrustManager trustManager = Launcher.getConfig().trustManager;
|
LauncherTrustManager trustManager = Launcher.getConfig().trustManager;
|
||||||
if (trustManager == null) return;
|
if (trustManager == null) return;
|
||||||
|
@ -58,8 +71,7 @@ public static void checkClass(Class<?> clazz) throws SecurityException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exitLauncher(int code)
|
public static void exitLauncher(int code) {
|
||||||
{
|
|
||||||
modulesManager.invokeEvent(new ClientExitPhase(code));
|
modulesManager.invokeEvent(new ClientExitPhase(code));
|
||||||
try {
|
try {
|
||||||
System.exit(code);
|
System.exit(code);
|
||||||
|
@ -108,7 +120,22 @@ public static void initGson(ClientModuleManager modulesManager) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verifyNoAgent() {
|
public static void verifyNoAgent() {
|
||||||
if (JVMHelper.RUNTIME_MXBEAN.getInputArguments().stream().filter(e -> e != null && !e.isEmpty()).anyMatch(e -> e.contains("javaagent"))) throw new SecurityException("JavaAgent found");
|
if (JVMHelper.RUNTIME_MXBEAN.getInputArguments().stream().filter(e -> e != null && !e.isEmpty()).anyMatch(e -> e.contains("javaagent")))
|
||||||
|
throw new SecurityException("JavaAgent found");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LauncherGuardInterface tryGetStdGuard() {
|
||||||
|
switch (Launcher.getConfig().guardType) {
|
||||||
|
case "no":
|
||||||
|
return new LauncherNoGuard();
|
||||||
|
case "wrapper":
|
||||||
|
return new LauncherWrapperGuard();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LauncherEngine clientInstance() {
|
||||||
|
return new LauncherEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readKeys() throws IOException, InvalidKeySpecException {
|
public void readKeys() throws IOException, InvalidKeySpecException {
|
||||||
|
@ -133,20 +160,6 @@ public void readKeys() throws IOException, InvalidKeySpecException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance
|
|
||||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
|
||||||
public RuntimeProvider runtimeProvider;
|
|
||||||
public ECPublicKey publicKey;
|
|
||||||
public ECPrivateKey privateKey;
|
|
||||||
|
|
||||||
public static ClientModuleManager modulesManager;
|
|
||||||
|
|
||||||
private LauncherEngine() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void start(String... args) throws Throwable {
|
public void start(String... args) throws Throwable {
|
||||||
//Launcher.modulesManager = new ClientModuleManager(this);
|
//Launcher.modulesManager = new ClientModuleManager(this);
|
||||||
LauncherEngine.guard = tryGetStdGuard();
|
LauncherEngine.guard = tryGetStdGuard();
|
||||||
|
@ -188,20 +201,4 @@ public void start(String... args) throws Throwable {
|
||||||
LogHelper.debug("Dir: %s", DirBridge.dir);
|
LogHelper.debug("Dir: %s", DirBridge.dir);
|
||||||
runtimeProvider.run(args);
|
runtimeProvider.run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LauncherGuardInterface tryGetStdGuard()
|
|
||||||
{
|
|
||||||
switch (Launcher.getConfig().guardType)
|
|
||||||
{
|
|
||||||
case "no":
|
|
||||||
return new LauncherNoGuard();
|
|
||||||
case "wrapper":
|
|
||||||
return new LauncherWrapperGuard();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LauncherEngine clientInstance() {
|
|
||||||
return new LauncherEngine();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
import pro.gravit.launcher.hasher.HashedDir;
|
import pro.gravit.launcher.hasher.HashedDir;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class NewLauncherSettings {
|
public class NewLauncherSettings {
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
public final transient List<HashedStoreEntry> lastHDirs = new ArrayList<>(16);
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
public Map<String, UserSettings> userSettings = new HashMap<>();
|
public Map<String, UserSettings> userSettings = new HashMap<>();
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
|
@ -14,6 +19,14 @@ public class NewLauncherSettings {
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
public String consoleUnlockKey;
|
public String consoleUnlockKey;
|
||||||
|
|
||||||
|
public void putHDir(String name, Path path, HashedDir dir) {
|
||||||
|
String fullPath = path.toAbsolutePath().toString();
|
||||||
|
lastHDirs.removeIf((e) -> e.fullPath.equals(fullPath) && e.name.equals(name));
|
||||||
|
HashedStoreEntry e = new HashedStoreEntry(dir, name, fullPath);
|
||||||
|
e.needSave = true;
|
||||||
|
lastHDirs.add(e);
|
||||||
|
}
|
||||||
|
|
||||||
public static class HashedStoreEntry {
|
public static class HashedStoreEntry {
|
||||||
public final HashedDir hdir;
|
public final HashedDir hdir;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
@ -26,15 +39,4 @@ public HashedStoreEntry(HashedDir hdir, String name, String fullPath) {
|
||||||
this.fullPath = fullPath;
|
this.fullPath = fullPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@LauncherNetworkAPI
|
|
||||||
public final transient List<HashedStoreEntry> lastHDirs = new ArrayList<>(16);
|
|
||||||
|
|
||||||
public void putHDir(String name, Path path, HashedDir dir) {
|
|
||||||
String fullPath = path.toAbsolutePath().toString();
|
|
||||||
lastHDirs.removeIf((e) -> e.fullPath.equals(fullPath) && e.name.equals(name));
|
|
||||||
HashedStoreEntry e = new HashedStoreEntry(dir, name, fullPath);
|
|
||||||
e.needSave = true;
|
|
||||||
lastHDirs.add(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,16 @@ public class SystemService {
|
||||||
private SystemService() {
|
private SystemService() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
public static void exit(int code)
|
|
||||||
{
|
public static void exit(int code) {
|
||||||
LauncherEngine.exitLauncher(code);
|
LauncherEngine.exitLauncher(code);
|
||||||
}
|
}
|
||||||
public static void setSecurityManager(SecurityManager s)
|
|
||||||
{
|
public static void setSecurityManager(SecurityManager s) {
|
||||||
LogHelper.debug("Try set security manager %s", s == null ? "null" : s.getClass().getName());
|
LogHelper.debug("Try set security manager %s", s == null ? "null" : s.getClass().getName());
|
||||||
if(AuthService.profile == null || AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.NONE) return;
|
if (AuthService.profile == null || AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.NONE)
|
||||||
if(AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.CLIENT)
|
return;
|
||||||
{
|
if (AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.CLIENT) {
|
||||||
System.setSecurityManager(s);
|
System.setSecurityManager(s);
|
||||||
}
|
}
|
||||||
//TODO NEXT
|
//TODO NEXT
|
||||||
|
|
|
@ -62,21 +62,21 @@ public ClientClassLoader(URL[] urls, ClassLoader parent) {
|
||||||
public String findLibrary(String name) {
|
public String findLibrary(String name) {
|
||||||
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(getNativePrefix()).concat(name).concat(getNativeEx());
|
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(getNativePrefix()).concat(name).concat(getNativeEx());
|
||||||
}
|
}
|
||||||
public String getNativeEx()
|
|
||||||
{
|
public String getNativeEx() {
|
||||||
if(JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
|
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
|
||||||
return ".dll";
|
return ".dll";
|
||||||
else if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
else if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
||||||
return ".so";
|
return ".so";
|
||||||
else if(JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
|
else if (JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
|
||||||
return ".dylib";
|
return ".dylib";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
public String getNativePrefix()
|
|
||||||
{
|
public String getNativePrefix() {
|
||||||
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
||||||
return "lib";
|
return "lib";
|
||||||
else if(JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
|
else if (JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
|
||||||
return "lib";
|
return "lib";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,10 @@
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.net.*;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -37,12 +40,12 @@
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class ClientLauncherEntryPoint {
|
public class ClientLauncherEntryPoint {
|
||||||
|
private static ClientClassLoader classLoader;
|
||||||
|
|
||||||
private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException {
|
private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException {
|
||||||
try (Socket socket = IOHelper.newSocket())
|
try (Socket socket = IOHelper.newSocket()) {
|
||||||
{
|
|
||||||
socket.connect(address);
|
socket.connect(address);
|
||||||
try(HInput input = new HInput(socket.getInputStream()))
|
try (HInput input = new HInput(socket.getInputStream())) {
|
||||||
{
|
|
||||||
byte[] serialized = input.readByteArray(0);
|
byte[] serialized = input.readByteArray(0);
|
||||||
ClientLauncherProcess.ClientParams params = Launcher.gsonManager.gson.fromJson(new String(serialized, IOHelper.UNICODE_CHARSET), ClientLauncherProcess.ClientParams.class);
|
ClientLauncherProcess.ClientParams params = Launcher.gsonManager.gson.fromJson(new String(serialized, IOHelper.UNICODE_CHARSET), ClientLauncherProcess.ClientParams.class);
|
||||||
params.clientHDir = new HashedDir(input);
|
params.clientHDir = new HashedDir(input);
|
||||||
|
@ -52,7 +55,7 @@ private static ClientLauncherProcess.ClientParams readParams(SocketAddress addre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static ClientClassLoader classLoader;
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
LauncherEngine.IS_CLIENT.set(true);
|
LauncherEngine.IS_CLIENT.set(true);
|
||||||
LauncherEngine engine = LauncherEngine.clientInstance();
|
LauncherEngine engine = LauncherEngine.clientInstance();
|
||||||
|
@ -147,10 +150,12 @@ public static void main(String[] args) throws Throwable {
|
||||||
launch(profile, params);
|
launch(profile, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initGson(ClientModuleManager moduleManager) {
|
private static void initGson(ClientModuleManager moduleManager) {
|
||||||
Launcher.gsonManager = new ClientGsonManager(moduleManager);
|
Launcher.gsonManager = new ClientGsonManager(moduleManager);
|
||||||
Launcher.gsonManager.initGson();
|
Launcher.gsonManager.initGson();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
|
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
|
||||||
//if (matcher != null)
|
//if (matcher != null)
|
||||||
// matcher = matcher.verifyOnly();
|
// matcher = matcher.verifyOnly();
|
||||||
|
@ -172,6 +177,7 @@ public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher,
|
||||||
throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir)));
|
throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkJVMBitsAndVersion() {
|
public static void checkJVMBitsAndVersion() {
|
||||||
if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) {
|
if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) {
|
||||||
String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS);
|
String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS);
|
||||||
|
@ -188,6 +194,7 @@ public static void checkJVMBitsAndVersion() {
|
||||||
JOptionPane.showMessageDialog(null, error);
|
JOptionPane.showMessageDialog(null, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedList<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException {
|
private static LinkedList<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException {
|
||||||
return resolveClassPathStream(clientDir, classPath).collect(Collectors.toCollection(LinkedList::new));
|
return resolveClassPathStream(clientDir, classPath).collect(Collectors.toCollection(LinkedList::new));
|
||||||
}
|
}
|
||||||
|
@ -204,20 +211,7 @@ private static Stream<Path> resolveClassPathStream(Path clientDir, String... cla
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
private static final class ClassPathFileVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
private final Stream.Builder<Path> result;
|
|
||||||
|
|
||||||
private ClassPathFileVisitor(Stream.Builder<Path> result) {
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
if (IOHelper.hasExtension(file, "jar") || IOHelper.hasExtension(file, "zip"))
|
|
||||||
result.accept(file);
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static void launch(ClientProfile profile, ClientLauncherProcess.ClientParams params) throws Throwable {
|
private static void launch(ClientProfile profile, ClientLauncherProcess.ClientParams params) throws Throwable {
|
||||||
// Add client args
|
// Add client args
|
||||||
Collection<String> args = new LinkedList<>();
|
Collection<String> args = new LinkedList<>();
|
||||||
|
@ -239,8 +233,7 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa
|
||||||
LogHelper.debug("Args: " + copy);
|
LogHelper.debug("Args: " + copy);
|
||||||
// Resolve main class and method
|
// Resolve main class and method
|
||||||
Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
|
Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
|
||||||
for(URL u : classLoader.getURLs())
|
for (URL u : classLoader.getURLs()) {
|
||||||
{
|
|
||||||
LogHelper.info("ClassLoader URL: %s", u.toString());
|
LogHelper.info("ClassLoader URL: %s", u.toString());
|
||||||
}
|
}
|
||||||
FMLPatcher.apply();
|
FMLPatcher.apply();
|
||||||
|
@ -260,4 +253,19 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ClassPathFileVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private final Stream.Builder<Path> result;
|
||||||
|
|
||||||
|
private ClassPathFileVisitor(Stream.Builder<Path> result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (IOHelper.hasExtension(file, "jar") || IOHelper.hasExtension(file, "zip"))
|
||||||
|
result.accept(file);
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,18 +24,19 @@
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ClientLauncherProcess {
|
public class ClientLauncherProcess {
|
||||||
private transient Process process;
|
|
||||||
private final transient Boolean[] waitWriteParams = new Boolean[] {false};
|
|
||||||
public Path executeFile;
|
|
||||||
public Path workDir;
|
|
||||||
public Path javaDir;
|
|
||||||
public final ClientParams params = new ClientParams();
|
public final ClientParams params = new ClientParams();
|
||||||
public final List<String> jvmArgs = new LinkedList<>();
|
public final List<String> jvmArgs = new LinkedList<>();
|
||||||
public final List<String> systemClientArgs = new LinkedList<>();
|
public final List<String> systemClientArgs = new LinkedList<>();
|
||||||
public final List<String> systemClassPath = new LinkedList<>();
|
public final List<String> systemClassPath = new LinkedList<>();
|
||||||
public final Map<String, String> systemEnv = new HashMap<>();
|
public final Map<String, String> systemEnv = new HashMap<>();
|
||||||
public final String mainClass;
|
public final String mainClass;
|
||||||
|
private final transient Boolean[] waitWriteParams = new Boolean[]{false};
|
||||||
|
public Path executeFile;
|
||||||
|
public Path workDir;
|
||||||
|
public Path javaDir;
|
||||||
|
public boolean useLegacyJavaClassPathProperty;
|
||||||
public boolean isStarted;
|
public boolean isStarted;
|
||||||
|
private transient Process process;
|
||||||
|
|
||||||
public ClientLauncherProcess(Path executeFile, Path workDir, Path javaDir, String mainClass) {
|
public ClientLauncherProcess(Path executeFile, Path workDir, Path javaDir, String mainClass) {
|
||||||
this.executeFile = executeFile;
|
this.executeFile = executeFile;
|
||||||
|
@ -74,8 +75,15 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir, Path r
|
||||||
this.params.javaHDir = jvmHDir;
|
this.params.javaHDir = jvmHDir;
|
||||||
applyClientProfile();
|
applyClientProfile();
|
||||||
}
|
}
|
||||||
private void applyClientProfile()
|
|
||||||
{
|
public static String getPathSeparator() {
|
||||||
|
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
|
||||||
|
return ";";
|
||||||
|
else
|
||||||
|
return ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyClientProfile() {
|
||||||
this.systemClassPath.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString());
|
this.systemClassPath.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString());
|
||||||
Collections.addAll(this.jvmArgs, this.params.profile.getJvmArgs());
|
Collections.addAll(this.jvmArgs, this.params.profile.getJvmArgs());
|
||||||
this.params.profile.pushOptionalJvmArgs(this.jvmArgs);
|
this.params.profile.pushOptionalJvmArgs(this.jvmArgs);
|
||||||
|
@ -89,9 +97,68 @@ private void applyClientProfile()
|
||||||
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderCreateEvent(this));
|
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderCreateEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void start(boolean pipeOutput) throws IOException, InterruptedException {
|
||||||
|
if (isStarted) throw new IllegalStateException("Process already started");
|
||||||
|
if (LauncherEngine.guard != null) LauncherEngine.guard.applyGuardParams(this);
|
||||||
|
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderPreLaunchEvent(this));
|
||||||
|
List<String> processArgs = new LinkedList<>();
|
||||||
|
processArgs.add(executeFile.toString());
|
||||||
|
processArgs.addAll(jvmArgs);
|
||||||
|
//ADD CLASSPATH
|
||||||
|
if (useLegacyJavaClassPathProperty) {
|
||||||
|
processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath)));
|
||||||
|
} else {
|
||||||
|
processArgs.add("-cp");
|
||||||
|
processArgs.add(String.join(getPathSeparator(), systemClassPath));
|
||||||
|
}
|
||||||
|
processArgs.add(mainClass);
|
||||||
|
processArgs.addAll(systemClientArgs);
|
||||||
|
synchronized (waitWriteParams) {
|
||||||
|
if (!waitWriteParams[0]) {
|
||||||
|
waitWriteParams.wait(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (LogHelper.isDebugEnabled())
|
||||||
|
LogHelper.debug("Commandline: %s", Arrays.toString(processArgs.toArray()));
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(processArgs);
|
||||||
|
EnvHelper.addEnv(processBuilder);
|
||||||
|
processBuilder.environment().put("JAVA_HOME", javaDir.toAbsolutePath().toString());
|
||||||
|
processBuilder.environment().putAll(systemEnv);
|
||||||
|
processBuilder.directory(workDir.toFile());
|
||||||
|
processBuilder.inheritIO();
|
||||||
|
if (pipeOutput) {
|
||||||
|
processBuilder.redirectErrorStream(true);
|
||||||
|
processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
|
||||||
|
}
|
||||||
|
process = processBuilder.start();
|
||||||
|
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderLaunchedEvent(this));
|
||||||
|
isStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ClientParams
|
public void runWriteParams(SocketAddress address) throws IOException {
|
||||||
{
|
try (ServerSocket serverSocket = new ServerSocket()) {
|
||||||
|
serverSocket.bind(address);
|
||||||
|
synchronized (waitWriteParams) {
|
||||||
|
waitWriteParams[0] = true;
|
||||||
|
waitWriteParams.notifyAll();
|
||||||
|
}
|
||||||
|
Socket socket = serverSocket.accept();
|
||||||
|
try (HOutput output = new HOutput(socket.getOutputStream())) {
|
||||||
|
byte[] serializedMainParams = Launcher.gsonManager.gson.toJson(params).getBytes(IOHelper.UNICODE_CHARSET);
|
||||||
|
output.writeByteArray(serializedMainParams, 0);
|
||||||
|
params.clientHDir.write(output);
|
||||||
|
params.assetHDir.write(output);
|
||||||
|
params.javaHDir.write(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Process getProcess() {
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClientParams {
|
||||||
public String assetDir;
|
public String assetDir;
|
||||||
|
|
||||||
public String clientDir;
|
public String clientDir;
|
||||||
|
@ -128,27 +195,13 @@ public static class ClientParams
|
||||||
|
|
||||||
public transient HashedDir javaHDir;
|
public transient HashedDir javaHDir;
|
||||||
|
|
||||||
public void addClientArgs(Collection<String> args)
|
public void addClientArgs(Collection<String> args) {
|
||||||
{
|
|
||||||
if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0)
|
if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0)
|
||||||
addModernClientArgs(args);
|
addModernClientArgs(args);
|
||||||
else
|
else
|
||||||
addClientLegacyArgs(args);
|
addClientLegacyArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class ClientUserProperties {
|
|
||||||
@LauncherNetworkAPI
|
|
||||||
public String[] skinURL;
|
|
||||||
@LauncherNetworkAPI
|
|
||||||
public String[] skinDigest;
|
|
||||||
@LauncherNetworkAPI
|
|
||||||
public String[] cloakURL;
|
|
||||||
@LauncherNetworkAPI
|
|
||||||
public String[] cloakDigest;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addClientLegacyArgs(Collection<String> args) {
|
public void addClientLegacyArgs(Collection<String> args) {
|
||||||
args.add(playerProfile.username);
|
args.add(playerProfile.username);
|
||||||
args.add(accessToken);
|
args.add(accessToken);
|
||||||
|
@ -211,74 +264,16 @@ private void addModernClientArgs(Collection<String> args) {
|
||||||
Collections.addAll(args, "--height", Integer.toString(height));
|
Collections.addAll(args, "--height", Integer.toString(height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
public void start(boolean pipeOutput) throws IOException, InterruptedException {
|
|
||||||
if(isStarted) throw new IllegalStateException("Process already started");
|
|
||||||
if(LauncherEngine.guard != null) LauncherEngine.guard.applyGuardParams(this);
|
|
||||||
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderPreLaunchEvent(this));
|
|
||||||
List<String> processArgs = new LinkedList<>();
|
|
||||||
processArgs.add(executeFile.toString());
|
|
||||||
processArgs.addAll(jvmArgs);
|
|
||||||
processArgs.add("-cp");
|
|
||||||
//ADD CLASSPATH
|
|
||||||
processArgs.add(String.join(getPathSeparator(), systemClassPath));
|
|
||||||
processArgs.add(mainClass);
|
|
||||||
processArgs.addAll(systemClientArgs);
|
|
||||||
synchronized (waitWriteParams)
|
|
||||||
{
|
|
||||||
if(!waitWriteParams[0])
|
|
||||||
{
|
|
||||||
waitWriteParams.wait(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(LogHelper.isDebugEnabled())
|
|
||||||
LogHelper.debug("Commandline: %s", Arrays.toString(processArgs.toArray()));
|
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(processArgs);
|
|
||||||
EnvHelper.addEnv(processBuilder);
|
|
||||||
processBuilder.environment().put("JAVA_HOME", javaDir.toAbsolutePath().toString());
|
|
||||||
processBuilder.environment().putAll(systemEnv);
|
|
||||||
processBuilder.directory(workDir.toFile());
|
|
||||||
processBuilder.inheritIO();
|
|
||||||
if (pipeOutput) {
|
|
||||||
processBuilder.redirectErrorStream(true);
|
|
||||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
|
|
||||||
}
|
|
||||||
process = processBuilder.start();
|
|
||||||
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderLaunchedEvent(this));
|
|
||||||
isStarted = true;
|
|
||||||
}
|
|
||||||
public void runWriteParams(SocketAddress address) throws IOException
|
|
||||||
{
|
|
||||||
try(ServerSocket serverSocket = new ServerSocket())
|
|
||||||
{
|
|
||||||
serverSocket.bind(address);
|
|
||||||
synchronized (waitWriteParams)
|
|
||||||
{
|
|
||||||
waitWriteParams[0] = true;
|
|
||||||
waitWriteParams.notifyAll();
|
|
||||||
}
|
|
||||||
Socket socket = serverSocket.accept();
|
|
||||||
try(HOutput output = new HOutput(socket.getOutputStream()))
|
|
||||||
{
|
|
||||||
byte[] serializedMainParams = Launcher.gsonManager.gson.toJson(params).getBytes(IOHelper.UNICODE_CHARSET);
|
|
||||||
output.writeByteArray(serializedMainParams, 0);
|
|
||||||
params.clientHDir.write(output);
|
|
||||||
params.assetHDir.write(output);
|
|
||||||
params.javaHDir.write(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Process getProcess() {
|
public static class ClientUserProperties {
|
||||||
return process;
|
@LauncherNetworkAPI
|
||||||
}
|
public String[] skinURL;
|
||||||
|
@LauncherNetworkAPI
|
||||||
public static String getPathSeparator()
|
public String[] skinDigest;
|
||||||
{
|
@LauncherNetworkAPI
|
||||||
if(JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
|
public String[] cloakURL;
|
||||||
return ";";
|
@LauncherNetworkAPI
|
||||||
else
|
public String[] cloakDigest;
|
||||||
return ":";
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package pro.gravit.launcher.client;
|
package pro.gravit.launcher.client;
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
|
import pro.gravit.launcher.LauncherTrustManager;
|
||||||
import pro.gravit.launcher.modules.LauncherModule;
|
import pro.gravit.launcher.modules.LauncherModule;
|
||||||
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
|
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
|
||||||
import pro.gravit.launcher.LauncherTrustManager;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
|
@ -29,12 +29,34 @@ public class DirBridge {
|
||||||
|
|
||||||
public static boolean useLegacyDir;
|
public static boolean useLegacyDir;
|
||||||
|
|
||||||
|
static {
|
||||||
public static void move(Path newDir) throws IOException {
|
String projectName = Launcher.getConfig().projectName;
|
||||||
IOHelper.move(dirUpdates, newDir);
|
try {
|
||||||
dirUpdates = newDir;
|
DirBridge.dir = getLauncherDir(projectName);
|
||||||
|
if (!IOHelper.exists(DirBridge.dir)) Files.createDirectories(DirBridge.dir);
|
||||||
|
DirBridge.defaultUpdatesDir = DirBridge.dir.resolve("updates");
|
||||||
|
if (!IOHelper.exists(DirBridge.defaultUpdatesDir)) Files.createDirectories(DirBridge.defaultUpdatesDir);
|
||||||
|
DirBridge.dirStore = getStoreDir(projectName);
|
||||||
|
if (!IOHelper.exists(DirBridge.dirStore)) Files.createDirectories(DirBridge.dirStore);
|
||||||
|
DirBridge.dirProjectStore = getProjectStoreDir(projectName);
|
||||||
|
if (!IOHelper.exists(DirBridge.dirProjectStore)) Files.createDirectories(DirBridge.dirProjectStore);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void move(Path newDir) throws IOException {
|
||||||
|
if (newDir == null) {
|
||||||
|
LogHelper.debug("Invalid dir (null)");
|
||||||
|
if (LogHelper.isDevEnabled())
|
||||||
|
LogHelper.dev(LogHelper.toString(new Throwable("Check stack of call DirBridge with null path...")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path oldUpdates = dirUpdates;
|
||||||
|
dirUpdates = newDir;
|
||||||
|
LogHelper.dev(newDir.toString());
|
||||||
|
IOHelper.move(oldUpdates, dirUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
public static Path getAppDataDir() throws IOException {
|
public static Path getAppDataDir() throws IOException {
|
||||||
boolean isCustomDir = Boolean.getBoolean(System.getProperty(USE_CUSTOMDIR_PROPERTY, "false"));
|
boolean isCustomDir = Boolean.getBoolean(System.getProperty(USE_CUSTOMDIR_PROPERTY, "false"));
|
||||||
|
@ -53,6 +75,10 @@ public static Path getAppDataDir() throws IOException {
|
||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
} else if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
|
} else if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
|
||||||
|
if (System.getenv().containsKey("appdata"))
|
||||||
|
return Paths.get(System.getenv().get("appdata")).toAbsolutePath();
|
||||||
|
if (System.getenv().containsKey("APPDATA")) // Because it is windows
|
||||||
|
return Paths.get(System.getenv().get("APPDATA")).toAbsolutePath();
|
||||||
Path appdata = IOHelper.HOME_DIR.resolve("AppData").resolve("Roaming");
|
Path appdata = IOHelper.HOME_DIR.resolve("AppData").resolve("Roaming");
|
||||||
if (!IOHelper.isDir(appdata)) Files.createDirectories(appdata);
|
if (!IOHelper.isDir(appdata)) Files.createDirectories(appdata);
|
||||||
return appdata;
|
return appdata;
|
||||||
|
@ -65,12 +91,10 @@ public static Path getAppDataDir() throws IOException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path getLauncherDir(String projectname) throws IOException {
|
public static Path getLauncherDir(String projectname) throws IOException {
|
||||||
return getAppDataDir().resolve(projectname);
|
return getAppDataDir().resolve(projectname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path getStoreDir(String projectname) throws IOException {
|
public static Path getStoreDir(String projectname) throws IOException {
|
||||||
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
|
||||||
return getAppDataDir().resolve("store");
|
return getAppDataDir().resolve("store");
|
||||||
|
@ -80,39 +104,19 @@ else if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
|
||||||
return getAppDataDir().resolve("minecraftStore");
|
return getAppDataDir().resolve("minecraftStore");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path getProjectStoreDir(String projectname) throws IOException {
|
public static Path getProjectStoreDir(String projectname) throws IOException {
|
||||||
return getStoreDir(projectname).resolve(projectname);
|
return getStoreDir(projectname).resolve(projectname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path getGuardDir() {
|
public static Path getGuardDir() {
|
||||||
return dir.resolve("guard");
|
return dir.resolve("guard");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Path getLegacyLauncherDir(String projectname) {
|
public static Path getLegacyLauncherDir(String projectname) {
|
||||||
return IOHelper.HOME_DIR.resolve(projectname);
|
return IOHelper.HOME_DIR.resolve(projectname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void setUseLegacyDir(boolean b) {
|
public static void setUseLegacyDir(boolean b) {
|
||||||
useLegacyDir = b;
|
useLegacyDir = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
|
||||||
String projectName = Launcher.getConfig().projectName;
|
|
||||||
try {
|
|
||||||
DirBridge.dir = getLauncherDir(projectName);
|
|
||||||
if (!IOHelper.exists(DirBridge.dir)) Files.createDirectories(DirBridge.dir);
|
|
||||||
DirBridge.defaultUpdatesDir = DirBridge.dir.resolve("updates");
|
|
||||||
if (!IOHelper.exists(DirBridge.defaultUpdatesDir)) Files.createDirectories(DirBridge.defaultUpdatesDir);
|
|
||||||
DirBridge.dirStore = getStoreDir(projectName);
|
|
||||||
if (!IOHelper.exists(DirBridge.dirStore)) Files.createDirectories(DirBridge.dirStore);
|
|
||||||
DirBridge.dirProjectStore = getProjectStoreDir(projectName);
|
|
||||||
if (!IOHelper.exists(DirBridge.dirProjectStore)) Files.createDirectories(DirBridge.dirProjectStore);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogHelper.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,34 +21,24 @@
|
||||||
|
|
||||||
public final class ServerPinger {
|
public final class ServerPinger {
|
||||||
|
|
||||||
public static final class Result {
|
|
||||||
|
|
||||||
public final int onlinePlayers;
|
|
||||||
|
|
||||||
public final int maxPlayers;
|
|
||||||
|
|
||||||
public final String raw;
|
|
||||||
|
|
||||||
public Result(int onlinePlayers, int maxPlayers, String raw) {
|
|
||||||
this.onlinePlayers = VerifyHelper.verifyInt(onlinePlayers,
|
|
||||||
VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0");
|
|
||||||
this.maxPlayers = VerifyHelper.verifyInt(maxPlayers,
|
|
||||||
VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0");
|
|
||||||
this.raw = raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isOverfilled() {
|
|
||||||
return onlinePlayers >= maxPlayers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
private static final String LEGACY_PING_HOST_MAGIC = "§1";
|
private static final String LEGACY_PING_HOST_MAGIC = "§1";
|
||||||
private static final String LEGACY_PING_HOST_CHANNEL = "MC|PingHost";
|
private static final String LEGACY_PING_HOST_CHANNEL = "MC|PingHost";
|
||||||
private static final Pattern LEGACY_PING_HOST_DELIMETER = Pattern.compile("\0", Pattern.LITERAL);
|
private static final Pattern LEGACY_PING_HOST_DELIMETER = Pattern.compile("\0", Pattern.LITERAL);
|
||||||
|
|
||||||
private static final int PACKET_LENGTH = 65535;
|
private static final int PACKET_LENGTH = 65535;
|
||||||
|
// Instance
|
||||||
|
private final InetSocketAddress address;
|
||||||
|
private final ClientProfile.Version version;
|
||||||
|
// Cache
|
||||||
|
private final Object cacheLock = new Object();
|
||||||
|
private Result cache = null;
|
||||||
|
private Exception cacheException = null;
|
||||||
|
private Instant cacheTime = null;
|
||||||
|
|
||||||
|
public ServerPinger(ClientProfile profile) {
|
||||||
|
this.address = Objects.requireNonNull(profile.getServerSocketAddress(), "address");
|
||||||
|
this.version = Objects.requireNonNull(profile.getVersion(), "version");
|
||||||
|
}
|
||||||
|
|
||||||
private static String readUTF16String(HInput input) throws IOException {
|
private static String readUTF16String(HInput input) throws IOException {
|
||||||
int length = input.readUnsignedShort() << 1;
|
int length = input.readUnsignedShort() << 1;
|
||||||
|
@ -61,24 +51,6 @@ private static void writeUTF16String(HOutput output, String s) throws IOExceptio
|
||||||
output.stream.write(s.getBytes(StandardCharsets.UTF_16BE));
|
output.stream.write(s.getBytes(StandardCharsets.UTF_16BE));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance
|
|
||||||
private final InetSocketAddress address;
|
|
||||||
private final ClientProfile.Version version;
|
|
||||||
// Cache
|
|
||||||
private final Object cacheLock = new Object();
|
|
||||||
|
|
||||||
private Result cache = null;
|
|
||||||
|
|
||||||
private Exception cacheException = null;
|
|
||||||
|
|
||||||
private Instant cacheTime = null;
|
|
||||||
|
|
||||||
|
|
||||||
public ServerPinger(ClientProfile profile) {
|
|
||||||
this.address = Objects.requireNonNull(profile.getServerSocketAddress(), "address");
|
|
||||||
this.version = Objects.requireNonNull(profile.getVersion(), "version");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result doPing() throws IOException {
|
private Result doPing() throws IOException {
|
||||||
try (Socket socket = IOHelper.newSocket()) {
|
try (Socket socket = IOHelper.newSocket()) {
|
||||||
socket.connect(IOHelper.resolve(address), IOHelper.SOCKET_TIMEOUT);
|
socket.connect(IOHelper.resolve(address), IOHelper.SOCKET_TIMEOUT);
|
||||||
|
@ -192,7 +164,6 @@ private Result modernPing(HInput input, HOutput output) throws IOException {
|
||||||
return new Result(online, max, response);
|
return new Result(online, max, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Result ping() throws IOException {
|
public Result ping() throws IOException {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
synchronized (cacheLock) {
|
synchronized (cacheLock) {
|
||||||
|
@ -222,4 +193,26 @@ public Result ping() throws IOException {
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Result {
|
||||||
|
|
||||||
|
public final int onlinePlayers;
|
||||||
|
|
||||||
|
public final int maxPlayers;
|
||||||
|
|
||||||
|
public final String raw;
|
||||||
|
|
||||||
|
public Result(int onlinePlayers, int maxPlayers, String raw) {
|
||||||
|
this.onlinePlayers = VerifyHelper.verifyInt(onlinePlayers,
|
||||||
|
VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0");
|
||||||
|
this.maxPlayers = VerifyHelper.verifyInt(maxPlayers,
|
||||||
|
VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0");
|
||||||
|
this.raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isOverfilled() {
|
||||||
|
return onlinePlayers >= maxPlayers;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import pro.gravit.launcher.LauncherEngine;
|
import pro.gravit.launcher.LauncherEngine;
|
||||||
import pro.gravit.launcher.client.ClientLauncherProcess;
|
import pro.gravit.launcher.client.ClientLauncherProcess;
|
||||||
import pro.gravit.launcher.modules.events.PostInitPhase;
|
import pro.gravit.launcher.modules.events.PostInitPhase;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
|
||||||
|
|
||||||
public class ClientProcessReadyEvent extends PostInitPhase {
|
public class ClientProcessReadyEvent extends PostInitPhase {
|
||||||
public final LauncherEngine clientInstance;
|
public final LauncherEngine clientInstance;
|
||||||
|
|
|
@ -21,8 +21,7 @@ public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 1);
|
verifyArgs(args, 1);
|
||||||
if (ConsoleManager.checkUnlockKey(args[0])) {
|
if (ConsoleManager.checkUnlockKey(args[0])) {
|
||||||
LogHelper.info("Unlock successful");
|
LogHelper.info("Unlock successful");
|
||||||
if(!ConsoleManager.unlock())
|
if (!ConsoleManager.unlock()) {
|
||||||
{
|
|
||||||
LogHelper.error("Console unlock canceled");
|
LogHelper.error("Console unlock canceled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package pro.gravit.launcher.console.test;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.utils.HWIDProvider;
|
||||||
|
import pro.gravit.utils.command.Command;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
public class PrintHardwareInfoCommand extends Command {
|
||||||
|
@Override
|
||||||
|
public String getArgsDescription() {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsageDescription() {
|
||||||
|
return "print your hardware info and timings";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(String... args) throws Exception {
|
||||||
|
LogHelper.info("Your Hardware ID:");
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
long currentTime;
|
||||||
|
HWIDProvider provider = new HWIDProvider();
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
LogHelper.info("Create HWIDProvider instance: %d ms", currentTime - startTime);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
int bitness = provider.getBitness();
|
||||||
|
long totalMemory = provider.getTotalMemory();
|
||||||
|
boolean isBattery = provider.isBattery();
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
LogHelper.info("Bitness: %d, totalMemory: %d(%.3f GB), battery %s, TIME: %d ms", bitness, totalMemory, (double) totalMemory / (1024.0 * 1024.0 * 1024.0), Boolean.toString(isBattery), currentTime - startTime);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
int logicalProcessors = provider.getProcessorLogicalCount();
|
||||||
|
int physicalProcessors = provider.getProcessorPhysicalCount();
|
||||||
|
long processorMaxFreq = provider.getProcessorMaxFreq();
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
LogHelper.info("Processors || logical: %d physical %d freq %d, TIME: %d ms", logicalProcessors, physicalProcessors, processorMaxFreq, currentTime - startTime);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
String hwDiskID = provider.getHWDiskID();
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
LogHelper.info("HWDiskID %s, TIME: %d ms", hwDiskID, currentTime - startTime);
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
String baseboardSerial = provider.getBaseboardSerialNumber();
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
LogHelper.info("BaseboardSerial %s, TIME: %d ms", baseboardSerial, currentTime - startTime);
|
||||||
|
LogHelper.info("Hardware ID end");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,5 @@
|
||||||
package pro.gravit.launcher.guard;
|
package pro.gravit.launcher.guard;
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
|
||||||
import pro.gravit.launcher.LauncherConfig;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class LauncherGuardManager {
|
public class LauncherGuardManager {
|
||||||
public static LauncherGuardInterface guard;
|
public static LauncherGuardInterface guard;
|
||||||
|
|
||||||
|
|
|
@ -10,20 +10,6 @@
|
||||||
|
|
||||||
public class LauncherWrapperGuard implements LauncherGuardInterface {
|
public class LauncherWrapperGuard implements LauncherGuardInterface {
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "wrapper";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applyGuardParams(ClientLauncherProcess process) {
|
|
||||||
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
|
|
||||||
String projectName = Launcher.getConfig().projectName;
|
|
||||||
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
|
|
||||||
process.executeFile = DirBridge.getGuardDir().resolve(wrapperUnpackName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LauncherWrapperGuard() {
|
public LauncherWrapperGuard() {
|
||||||
try {
|
try {
|
||||||
String wrapperName = JVMHelper.JVM_BITS == 64 ? "wrapper64.exe" : "wrapper32.exe";
|
String wrapperName = JVMHelper.JVM_BITS == 64 ? "wrapper64.exe" : "wrapper32.exe";
|
||||||
|
@ -36,4 +22,19 @@ public LauncherWrapperGuard() {
|
||||||
throw new SecurityException(e);
|
throw new SecurityException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "wrapper";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyGuardParams(ClientLauncherProcess process) {
|
||||||
|
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
|
||||||
|
String projectName = Launcher.getConfig().projectName;
|
||||||
|
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
|
||||||
|
process.executeFile = DirBridge.getGuardDir().resolve(wrapperUnpackName);
|
||||||
|
process.useLegacyJavaClassPathProperty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import pro.gravit.launcher.LauncherEngine;
|
import pro.gravit.launcher.LauncherEngine;
|
||||||
import pro.gravit.launcher.client.events.ClientUnlockConsoleEvent;
|
import pro.gravit.launcher.client.events.ClientUnlockConsoleEvent;
|
||||||
import pro.gravit.launcher.console.UnlockCommand;
|
import pro.gravit.launcher.console.UnlockCommand;
|
||||||
|
import pro.gravit.launcher.console.test.PrintHardwareInfoCommand;
|
||||||
import pro.gravit.utils.command.CommandHandler;
|
import pro.gravit.utils.command.CommandHandler;
|
||||||
import pro.gravit.utils.command.JLineCommandHandler;
|
import pro.gravit.utils.command.JLineCommandHandler;
|
||||||
import pro.gravit.utils.command.StdCommandHandler;
|
import pro.gravit.utils.command.StdCommandHandler;
|
||||||
|
@ -44,6 +45,7 @@ public static void registerCommands() {
|
||||||
handler.registerCommand("gc", new GCCommand());
|
handler.registerCommand("gc", new GCCommand());
|
||||||
handler.registerCommand("clear", new ClearCommand(handler));
|
handler.registerCommand("clear", new ClearCommand(handler));
|
||||||
handler.registerCommand("unlock", new UnlockCommand());
|
handler.registerCommand("unlock", new UnlockCommand());
|
||||||
|
handler.registerCommand("printhardware", new PrintHardwareInfoCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkUnlockKey(String key) {
|
public static boolean checkUnlockKey(String key) {
|
||||||
|
@ -51,10 +53,10 @@ public static boolean checkUnlockKey(String key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean unlock() {
|
public static boolean unlock() {
|
||||||
if(isConsoleUnlock) return true;
|
if (isConsoleUnlock) return true;
|
||||||
ClientUnlockConsoleEvent event = new ClientUnlockConsoleEvent(handler);
|
ClientUnlockConsoleEvent event = new ClientUnlockConsoleEvent(handler);
|
||||||
LauncherEngine.modulesManager.invokeEvent(event);
|
LauncherEngine.modulesManager.invokeEvent(event);
|
||||||
if(event.isCancel()) return false;
|
if (event.isCancel()) return false;
|
||||||
handler.registerCommand("debug", new DebugCommand());
|
handler.registerCommand("debug", new DebugCommand());
|
||||||
handler.unregisterCommand("unlock");
|
handler.unregisterCommand("unlock");
|
||||||
isConsoleUnlock = true;
|
isConsoleUnlock = true;
|
||||||
|
|
|
@ -10,10 +10,68 @@
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
public class SettingsManager extends JsonConfigurable<NewLauncherSettings> {
|
public class SettingsManager extends JsonConfigurable<NewLauncherSettings> {
|
||||||
|
public static NewLauncherSettings settings;
|
||||||
|
|
||||||
|
|
||||||
|
public SettingsManager() {
|
||||||
|
super(NewLauncherSettings.class, DirBridge.dir.resolve("settings.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NewLauncherSettings getConfig() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConfig(NewLauncherSettings config) {
|
||||||
|
settings = config;
|
||||||
|
if (settings.consoleUnlockKey != null && !ConsoleManager.isConsoleUnlock) {
|
||||||
|
if (ConsoleManager.checkUnlockKey(settings.consoleUnlockKey)) {
|
||||||
|
ConsoleManager.unlock();
|
||||||
|
LogHelper.info("Console auto unlocked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NewLauncherSettings getDefaultConfig() {
|
||||||
|
return new NewLauncherSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadHDirStore(Path storePath) throws IOException {
|
||||||
|
Files.createDirectories(storePath);
|
||||||
|
IOHelper.walk(storePath, new StoreFileVisitor(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveHDirStore(Path storeProjectPath) throws IOException {
|
||||||
|
Files.createDirectories(storeProjectPath);
|
||||||
|
for (NewLauncherSettings.HashedStoreEntry e : settings.lastHDirs) {
|
||||||
|
if (!e.needSave) continue;
|
||||||
|
Path file = storeProjectPath.resolve(e.name.concat(".bin"));
|
||||||
|
if (!Files.exists(file)) Files.createFile(file);
|
||||||
|
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
|
||||||
|
output.writeString(e.name, 128);
|
||||||
|
output.writeString(e.fullPath, 1024);
|
||||||
|
e.hdir.write(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadHDirStore() throws IOException {
|
||||||
|
loadHDirStore(DirBridge.dirStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveHDirStore() throws IOException {
|
||||||
|
saveHDirStore(DirBridge.dirProjectStore);
|
||||||
|
}
|
||||||
|
|
||||||
public static class StoreFileVisitor extends SimpleFileVisitor<Path> {
|
public static class StoreFileVisitor extends SimpleFileVisitor<Path> {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||||
|
@ -30,66 +88,4 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static NewLauncherSettings settings;
|
|
||||||
|
|
||||||
public SettingsManager() {
|
|
||||||
super(NewLauncherSettings.class, DirBridge.dir.resolve("settings.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NewLauncherSettings getConfig() {
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NewLauncherSettings getDefaultConfig() {
|
|
||||||
return new NewLauncherSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setConfig(NewLauncherSettings config) {
|
|
||||||
settings = config;
|
|
||||||
if (settings.consoleUnlockKey != null && !ConsoleManager.isConsoleUnlock) {
|
|
||||||
if (ConsoleManager.checkUnlockKey(settings.consoleUnlockKey)) {
|
|
||||||
ConsoleManager.unlock();
|
|
||||||
LogHelper.info("Console auto unlocked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void loadHDirStore(Path storePath) throws IOException {
|
|
||||||
Files.createDirectories(storePath);
|
|
||||||
IOHelper.walk(storePath, new StoreFileVisitor(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void saveHDirStore(Path storeProjectPath) throws IOException {
|
|
||||||
Files.createDirectories(storeProjectPath);
|
|
||||||
for (NewLauncherSettings.HashedStoreEntry e : settings.lastHDirs) {
|
|
||||||
if (!e.needSave) continue;
|
|
||||||
Path file = storeProjectPath.resolve(e.name.concat(".bin"));
|
|
||||||
if (!Files.exists(file)) Files.createFile(file);
|
|
||||||
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
|
|
||||||
output.writeString(e.name, 128);
|
|
||||||
output.writeString(e.fullPath, 1024);
|
|
||||||
e.hdir.write(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void loadHDirStore() throws IOException {
|
|
||||||
loadHDirStore(DirBridge.dirStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void saveHDirStore() throws IOException {
|
|
||||||
saveHDirStore(DirBridge.dirProjectStore);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
|
|
||||||
public class FMLPatcher extends ClassLoader implements Opcodes {
|
public class FMLPatcher extends ClassLoader implements Opcodes {
|
||||||
public static final MethodType EXITMH = MethodType.methodType(void.class, int.class);
|
public static final MethodType EXITMH = MethodType.methodType(void.class, int.class);
|
||||||
public static volatile FMLPatcher INSTANCE = null;
|
|
||||||
public static final String[] PACKAGES = new String[]{"cpw.mods.fml.", "net.minecraftforge.fml.", "cpw.mods."};
|
public static final String[] PACKAGES = new String[]{"cpw.mods.fml.", "net.minecraftforge.fml.", "cpw.mods."};
|
||||||
public static final Vector<MethodHandle> MHS = new Vector<>();
|
public static final Vector<MethodHandle> MHS = new Vector<>();
|
||||||
|
public static volatile FMLPatcher INSTANCE = null;
|
||||||
|
|
||||||
|
public FMLPatcher(final ClassLoader cl) {
|
||||||
|
super(cl);
|
||||||
|
}
|
||||||
|
|
||||||
public static void apply() {
|
public static void apply() {
|
||||||
INSTANCE = new FMLPatcher(null); // Never cause ClassFormatError (fuck forge 1.14!!!)
|
INSTANCE = new FMLPatcher(null); // Never cause ClassFormatError (fuck forge 1.14!!!)
|
||||||
|
@ -97,10 +101,6 @@ public static String randomStr(final int lenght) {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FMLPatcher(final ClassLoader cl) {
|
|
||||||
super(cl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> def(final String name, final String exName) {
|
public Class<?> def(final String name, final String exName) {
|
||||||
return super.defineClass(name, ByteBuffer.wrap(gen(name.replace('.', '/'), exName)), null);
|
return super.defineClass(name, ByteBuffer.wrap(gen(name.replace('.', '/'), exName)), null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,60 +18,20 @@
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class DirWatcher implements Runnable, AutoCloseable {
|
public final class DirWatcher implements Runnable, AutoCloseable {
|
||||||
private final class RegisterFileVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
|
||||||
FileVisitResult result = super.preVisitDirectory(dir, attrs);
|
|
||||||
if (DirWatcher.this.dir.equals(dir)) {
|
|
||||||
dir.register(service, KINDS);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe it's unnecessary to go deeper
|
|
||||||
//if (matcher != null && !matcher.shouldVerify(path)) {
|
|
||||||
// return FileVisitResult.SKIP_SUBTREE;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Register
|
|
||||||
dir.register(service, KINDS);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final boolean FILE_TREE_SUPPORTED = JVMHelper.OS_TYPE == OS.MUSTDIE;
|
public static final boolean FILE_TREE_SUPPORTED = JVMHelper.OS_TYPE == OS.MUSTDIE;
|
||||||
|
public static final String IGN_OVERFLOW = "launcher.dirwatcher.ignoreOverflows";
|
||||||
// Constants
|
// Constants
|
||||||
private static final Kind<?>[] KINDS = {
|
private static final Kind<?>[] KINDS = {
|
||||||
StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE
|
StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE
|
||||||
};
|
};
|
||||||
|
private static final boolean PROP_IGN_OVERFLOW = Boolean.parseBoolean(System.getProperty(IGN_OVERFLOW, "true"));
|
||||||
public static final String IGN_OVERFLOW = "launcher.dirwatcher.ignoreOverflows";
|
|
||||||
private static final boolean PROP_IGN_OVERFLOW = Boolean.parseBoolean(System.getProperty(IGN_OVERFLOW, "true"));
|
|
||||||
|
|
||||||
private static void handleError(Throwable e) {
|
|
||||||
LogHelper.error(e);
|
|
||||||
NativeJVMHalt.haltA(-123);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Deque<String> toPath(Iterable<Path> path) {
|
|
||||||
Deque<String> result = new LinkedList<>();
|
|
||||||
for (Path pe : path)
|
|
||||||
result.add(pe.toString());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
private final Path dir;
|
private final Path dir;
|
||||||
private final HashedDir hdir;
|
private final HashedDir hdir;
|
||||||
|
|
||||||
private final FileNameMatcher matcher;
|
private final FileNameMatcher matcher;
|
||||||
|
|
||||||
private final WatchService service;
|
private final WatchService service;
|
||||||
|
|
||||||
private final boolean digest;
|
private final boolean digest;
|
||||||
|
|
||||||
|
|
||||||
public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
|
public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
|
||||||
this.dir = Objects.requireNonNull(dir, "dir");
|
this.dir = Objects.requireNonNull(dir, "dir");
|
||||||
this.hdir = Objects.requireNonNull(hdir, "hdir");
|
this.hdir = Objects.requireNonNull(hdir, "hdir");
|
||||||
|
@ -84,6 +44,18 @@ public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean dig
|
||||||
LogHelper.subInfo("DirWatcher %s", dir.toString());
|
LogHelper.subInfo("DirWatcher %s", dir.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void handleError(Throwable e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
NativeJVMHalt.haltA(-123);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Deque<String> toPath(Iterable<Path> path) {
|
||||||
|
Deque<String> result = new LinkedList<>();
|
||||||
|
for (Path pe : path)
|
||||||
|
result.add(pe.toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
service.close();
|
service.close();
|
||||||
|
@ -136,4 +108,25 @@ public void run() {
|
||||||
handleError(exc);
|
handleError(exc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class RegisterFileVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||||
|
FileVisitResult result = super.preVisitDirectory(dir, attrs);
|
||||||
|
if (DirWatcher.this.dir.equals(dir)) {
|
||||||
|
dir.register(service, KINDS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe it's unnecessary to go deeper
|
||||||
|
//if (matcher != null && !matcher.shouldVerify(path)) {
|
||||||
|
// return FileVisitResult.SKIP_SUBTREE;
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Register
|
||||||
|
dir.register(service, KINDS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue