Merge branch 'dev' of github.com:GravitLauncher/Launcher into dev

This commit is contained in:
Gravit 2020-04-07 18:14:09 +07:00
commit bf932a4893
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
174 changed files with 3753 additions and 4272 deletions

View file

@ -1,5 +1,5 @@
name: push
on:
on:
push:
create:
tags:
@ -9,74 +9,74 @@ jobs:
name: Launcher
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Cache Gradle
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Cache Gradle
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Build with Gradle
run: ./gradlew build
- name: Create artifacts
run: |
mkdir -p artifacts/modules
cd LaunchServer/build/libs/
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
cd ../../../ServerWrapper/build/libs
cp ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
cd ../../../LauncherAuthlib/build/libs
cp LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
cd ../../../
cp modules/*_module/build/libs/*.jar artifacts/modules
cp modules/*_swmodule/build/libs/*.jar artifacts/modules
cp modules/*_lmodule/build/libs/*.jar artifacts/modules
- name: Create artifacts
run: |
mkdir -p artifacts/modules
cd LaunchServer/build/libs/
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
cd ../../../ServerWrapper/build/libs
cp ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
cd ../../../LauncherAuthlib/build/libs
cp LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
cd ../../../
cp modules/*_module/build/libs/*.jar artifacts/modules
cp modules/*_swmodule/build/libs/*.jar artifacts/modules
cp modules/*_lmodule/build/libs/*.jar artifacts/modules
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: Launcher
path: artifacts
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: Launcher
path: artifacts
- name: Create release
id: create_release
uses: actions/create-release@v1
if: github.event_name == 'create'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: GravitLauncher ${{ github.ref }}
draft: false
prerelease: false
- name: Create release
id: create_release
uses: actions/create-release@v1
if: github.event_name == 'create'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: GravitLauncher ${{ github.ref }}
draft: false
prerelease: false
- name: Pack release
if: github.event_name == 'create'
run: |
cd artifacts/
zip -r -9 ../Release.zip *
- name: Pack release
if: github.event_name == 'create'
run: |
cd artifacts/
zip -r -9 ../Release.zip *
- name: Upload release
if: github.event_name == 'create'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./Release.zip
asset_name: Release.zip
asset_content_type: application/zip
- name: Upload release
if: github.event_name == 'create'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./Release.zip
asset_name: Release.zip
asset_content_type: application/zip

2
.gitignore vendored
View file

@ -107,6 +107,6 @@ buildnumber
*.directory
cmd.bat
cmd.sh
project/target
## PVS Studio
.PVS-Studio/
project/target

View file

@ -70,7 +70,7 @@ task cleanjar(type: Jar, dependsOn: jar) {
dependencies {
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-reader', 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) {
duplicatesStrategy = 'EXCLUDE'
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
eachFile { FileCopyDetails fcp ->
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
fcp.relativePath.pathString.contains("workdir")) {
def segments = fcp.relativePath.segments
def pathSegments = segments[1..-1] as String[]
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
fcp.mode = 0755
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
fcp.relativePath.pathString.contains("workdir")) {
def segments = fcp.relativePath.segments
def pathSegments = segments[1..-1] as String[]
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
fcp.mode = 0755
}
}

View file

@ -51,215 +51,48 @@
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
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;
}
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 static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
public final Path dir;
public final LaunchServerEnv env;
public final Path launcherLibraries;
public final Path launcherLibrariesCompile;
public final Path caCertFile;
// Constant paths
public final Path caKeyFile;
public final Path serverCertFile;
public final Path serverKeyFile;
public final Path updatesDir;
public final LaunchServerConfigManager launchServerConfigManager;
//public static LaunchServer server = null;
public final Path profilesDir;
// Server config
public LaunchServerConfig config;
public final LaunchServerRuntimeConfig runtime;
public final ECPublicKey publicKey;
public final ECPrivateKey privateKey;
// Launcher binary
public final JARLauncherBinary launcherBinary;
//public static LaunchServer server = null;
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
// Server config
public final LauncherBinary launcherEXEBinary;
// HWID ban + anti-brutforce
public final SessionManager sessionManager;
public final AuthHookManager authHookManager;
// Server
public final LaunchServerModulesManager modulesManager;
// Launcher binary
public final MirrorManager mirrorManager;
public final ReconfigurableManager reconfigurableManager;
public final ConfigManager configManager;
// HWID ban + anti-brutforce
public final CertificateManager certificateManager;
public final ProguardConf proguardConf;
// Server
public final CommandHandler commandHandler;
public final NettyServerSocketHandler nettyServerSocketHandler;
public final Timer taskPool;
private final AtomicBoolean started = new AtomicBoolean(false);
public LaunchServerConfig config;
public volatile Map<String, HashedDir> updatesDirMap;
// Updates and profiles
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 {
this.dir = directories.dir;
@ -389,6 +222,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() {
if (launcherEXEBinaryClass != null) {
try {
@ -406,7 +306,6 @@ private LauncherBinary binary() {
return new EXELauncherBinary(this);
}
public void buildLauncherBinaries() throws IOException {
launcherBinary.build();
launcherEXEBinary.build();
@ -435,7 +334,6 @@ public HashedDir getUpdateDir(String name) {
return updatesDirMap.get(name);
}
public Set<Entry<String, HashedDir>> getUpdateDirs() {
return updatesDirMap.entrySet();
}
@ -467,7 +365,6 @@ public void run() {
modulesManager.invokeEvent(new LaunchServerFullInitEvent(this));
}
public void syncLauncherBinaries() throws IOException {
LogHelper.info("Syncing launcher binaries");
@ -482,7 +379,6 @@ public void syncLauncherBinaries() throws IOException {
}
public void syncProfilesDir() throws IOException {
LogHelper.info("Syncing profiles dir");
List<ClientProfile> newProfies = new LinkedList<>();
@ -493,7 +389,6 @@ public void syncProfilesDir() throws IOException {
profilesList = Collections.unmodifiableList(newProfies);
}
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
LogHelper.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
@ -565,4 +460,73 @@ public void fullyRestart() {
restart();
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);
}
}
}

View file

@ -2,6 +2,7 @@
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.handler.AuthHandler;
@ -23,7 +24,6 @@
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.launcher.LauncherTrustManager;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -177,9 +177,9 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOExcept
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
if (inDocker) {
Path parentLibraries = StarterAgent.libraries.toAbsolutePath().normalize().getParent();
directories.launcherLibrariesCompileDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIESCOMPILE_NAME);
directories.launcherLibrariesDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIES_NAME);
Path parentLibraries = StarterAgent.libraries.toAbsolutePath().normalize().getParent();
directories.launcherLibrariesCompileDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIESCOMPILE_NAME);
directories.launcherLibrariesDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIES_NAME);
}
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)

View file

@ -6,15 +6,30 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.jar.JarFile;
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 Set<PosixFilePermission> DPERMS;
@ -49,23 +64,4 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
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);
}
}
}

View file

@ -18,35 +18,20 @@
* чего угодно. Работает через поиск class-файлов в classpath.
*/
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;
public ClassMetadataReader(List<JarFile> cp) {
this.cp = cp;
}
public List<JarFile> getCp() {
return cp;
}
public ClassMetadataReader() {
this.cp = new ArrayList<>();
}
public List<JarFile> getCp() {
return cp;
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
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);
}
public byte[] getClassData(String className) throws IOException {
for (JarFile f : cp) {
if (f.getEntry(className + ".class") != null) {
@ -111,4 +95,19 @@ public void close() {
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;
}
}
}

View file

@ -1,245 +1,243 @@
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.Opcodes;
import org.objectweb.asm.Type;
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.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")
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) {
this.values = values;
}
static {
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,
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 final Map<String, Object> values;
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);
}
}
}
});
}
public InjectClassAcceptor(Map<String, Object> values) {
this.values = values;
}
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 {
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 Serializer<?> serializerClass(int opcode) {
return new Serializer<Number>() {
@Override
public InsnList serialize(Number value) {
InsnList ret = new InsnList();
ret.add(NodeUtils.push(value.intValue()));
ret.add(new InsnNode(opcode));
return ret;
}
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;
}
};
}
@FunctionalInterface
private interface Serializer<T> {
InsnList serialize(T value);
}
};
}
@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()));
}
@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()));
}
private static class ListSerializer implements Serializer<List> {
@Override
public InsnList serialize(List value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(NodeUtils.push(value.size()));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
for (Object object : value) {
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(object));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
@Override
public void transform(ClassNode classNode, String className, BuildContext context) {
visit(classNode, values);
}
private static class MapSerializer implements Serializer<Map> {
@Override
public InsnList serialize(Map value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE), false));
for (Object entryObject : value.entrySet()) {
Map.Entry entry = (Map.Entry) entryObject;
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(entry.getKey()));
insnList.add(serializeValue(entry.getValue()));
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)),
true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
@FunctionalInterface
private interface Serializer<T> {
InsnList serialize(T value);
}
private static class ByteArraySerializer implements Serializer<byte[]> {
@Override
public InsnList serialize(byte[] value) {
InsnList insnList = new InsnList();
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
false));
return insnList;
}
}
private static class ListSerializer implements Serializer<List> {
@Override
public InsnList serialize(List value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(NodeUtils.push(value.size()));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
for (Object object : value) {
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(object));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
@Override
public void transform(ClassNode classNode, String className, BuildContext context) {
visit(classNode, values);
}
private static class MapSerializer implements Serializer<Map> {
@Override
public InsnList serialize(Map value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE), false));
for (Object entryObject : value.entrySet()) {
Map.Entry entry = (Map.Entry) entryObject;
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(entry.getKey()));
insnList.add(serializeValue(entry.getValue()));
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)),
true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
private static class ByteArraySerializer implements Serializer<byte[]> {
@Override
public InsnList serialize(byte[] value) {
InsnList insnList = new InsnList();
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
false));
return insnList;
}
}
}

View file

@ -17,6 +17,8 @@
public final class NodeUtils {
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
private NodeUtils() {
}
@ -186,8 +188,6 @@ public static InsnList getSafeStringInsnList(String string) {
return insnList;
}
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
public static String[] splitUtf8ToChunks(String text, int maxBytes) {
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) {
InsnList ret = new InsnList();
Type e = Type.getType(u.getClass());
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
return ret;
}
InsnList ret = new InsnList();
Type e = Type.getType(u.getClass());
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
return ret;
}
}

View file

@ -9,13 +9,13 @@
import java.util.Map;
public class AuthProviderPair {
public final boolean isDefault = true;
public AuthProvider provider;
public AuthHandler handler;
public TextureProvider textureProvider;
public Map<String, String> links;
public transient String name;
public String displayName;
public final boolean isDefault = true;
public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvider textureProvider) {
this.provider = provider;
@ -24,35 +24,33 @@ public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvi
}
public void init(LaunchServer srv, String name) {
this.name = name;
if(links != null) link(srv);
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(textureProvider == null) throw new NullPointerException(String.format("Auth %s textureProvider null", name));
this.name = name;
if (links != null) link(srv);
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 (textureProvider == null)
throw new NullPointerException(String.format("Auth %s textureProvider null", name));
provider.init(srv);
handler.init(srv);
}
public void link(LaunchServer srv)
{
links.forEach((k,v) -> {
public void link(LaunchServer srv) {
links.forEach((k, 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));
}
if("provider".equals(k))
{
if(pair.provider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.provider is null", name, v));
if ("provider".equals(k)) {
if (pair.provider == null)
throw new NullPointerException(String.format("Auth %s link failed. %s.provider is null", name, v));
provider = pair.provider;
}
else if("handler".equals(k))
{
if(pair.handler == null) throw new NullPointerException(String.format("Auth %s link failed. %s.handler is null", name, v));
} else if ("handler".equals(k)) {
if (pair.handler == null)
throw new NullPointerException(String.format("Auth %s link failed. %s.handler is null", name, v));
handler = pair.handler;
}
else if("textureProvider".equals(k))
{
if(pair.textureProvider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.textureProvider is null", name, v));
} else if ("textureProvider".equals(k)) {
if (pair.textureProvider == null)
throw new NullPointerException(String.format("Auth %s link failed. %s.textureProvider is null", name, v));
textureProvider = pair.textureProvider;
}
});

View file

@ -21,8 +21,8 @@ public final class PostgreSQLSourceConfig implements AutoCloseable {
private String poolName;
// Config
private String address;
private int port;
private String[] addresses;
private int[] ports;
private String username;
private String password;
private String database;
@ -43,8 +43,8 @@ public synchronized Connection getConnection() throws SQLException {
PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
// Set credentials
postgresqlSource.setServerNames(new String[] {address}); //TODO support multinode PostgreSQL DB
postgresqlSource.setPortNumbers(new int[] {port});
postgresqlSource.setServerNames(addresses);
postgresqlSource.setPortNumbers(ports);
postgresqlSource.setUser(username);
postgresqlSource.setPassword(password);
postgresqlSource.setDatabaseName(database);

View file

@ -12,7 +12,7 @@
public abstract class AuthHandler implements AutoCloseable {
public static final ProviderMap<AuthHandler> providers = new ProviderMap<>("AuthHandler");
private static boolean registredHandl = false;
protected transient LaunchServer srv;
public static UUID authError(String message) throws AuthException {
throw new AuthException(message);
@ -31,8 +31,6 @@ public static void registerHandlers() {
}
}
protected transient LaunchServer srv;
/**
* Returns the UUID associated with the account
*

View file

@ -18,26 +18,8 @@
import java.util.UUID;
public abstract class CachedAuthHandler extends AuthHandler implements NeedGarbageCollection, Reconfigurable {
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;
}
private transient final Map<UUID, Entry> entryCache = new HashMap<>(1024);
private transient final Map<String, UUID> usernamesCache = new HashMap<>(1024);
@Override
public Map<String, Command> getCommands() {
@ -91,10 +73,6 @@ public void invoke(String... args) throws Exception {
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) {
Entry previous = entryCache.put(entry.uuid, entry);
if (previous != null)
@ -122,10 +100,8 @@ public synchronized UUID checkServer(String username, String serverID) throws IO
serverID.equals(entry.serverID) ? entry.uuid : null;
}
protected abstract Entry fetchEntry(String username) throws IOException;
protected abstract Entry fetchEntry(UUID uuid) 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 updateServerID(UUID uuid, String serverID) throws IOException;
@Override
@ -201,4 +176,25 @@ public final synchronized String uuidToUsername(UUID uuid) throws IOException {
Entry entry = getEntry(uuid);
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;
}
}

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.HTTPRequest;
import pro.gravit.launcher.Launcher;
import java.io.IOException;
import java.net.URL;
@ -12,6 +12,31 @@ public class JsonAuthHandler extends CachedAuthHandler {
public URL updateAuthUrl;
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 final String username;
@ -53,29 +78,4 @@ public UpdateServerIDRequest(UUID uuid, String serverID) {
public static class SuccessResponse {
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() {
}
}

View file

@ -10,14 +10,12 @@
import java.util.UUID;
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 goodResponse = "OK";
private String usernameFetch;
private String uuidFetch;
private String updateAuth;
private String updateServerID;
@Override
public void init(LaunchServer srv) {

View file

@ -4,7 +4,6 @@
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
public class AdvancedProtectHandler extends ProtectHandler implements SecureProtectHandler {

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
public class NoProtectHandler extends ProtectHandler {

View file

@ -4,7 +4,6 @@
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.util.ArrayList;
import java.util.HashMap;
@ -14,6 +13,7 @@
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
public Map<String, List<String>> profileWhitelist = new HashMap<>();
public List<String> allowUpdates = new ArrayList<>();
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
@ -36,13 +36,12 @@ public boolean canChangeProfile(ClientProfile profile, Client client) {
@Override
public boolean canGetUpdates(String updatesDirName, Client client) {
return client.profile != null && ( client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
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);
if(allowedUsername == null) return true;
if (allowedUsername == null) return true;
return allowedUsername.contains(username);
}
}

View file

@ -4,20 +4,19 @@
import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler {
default boolean canGetProfiles(Client client)
{
default boolean canGetProfiles(Client client) {
return true;
}
default boolean canGetProfile(ClientProfile profile, Client client)
{
default boolean canGetProfile(ClientProfile profile, Client client) {
return true;
}
default boolean canChangeProfile(ClientProfile profile, Client client)
{
default boolean canChangeProfile(ClientProfile profile, Client client) {
return client.isAuth;
}
default boolean canGetUpdates(String updatesDirName, Client client)
{
default boolean canGetUpdates(String updatesDirName, Client client) {
return true;
}
}

View file

@ -12,21 +12,23 @@
import java.security.spec.InvalidKeySpecException;
public interface SecureProtectHandler {
default byte[] generateSecureLevelKey()
{
default byte[] generateSecureLevelKey() {
return SecurityHelper.randomBytes(128);
}
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);
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
sign.update(data);
sign.verify(signature);
}
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
boolean allowGetSecureLevelInfo(Client client);
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client)
{
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client) {
return new SecurityReportRequestEvent();
}
}

View file

@ -13,13 +13,6 @@ public abstract class AuthProvider implements AutoCloseable {
private static boolean registredProv = false;
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 {
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

View file

@ -3,10 +3,10 @@
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.HTTPRequest;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launcher.HTTPRequest;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
@ -17,33 +17,6 @@ public final class JsonAuthProvider extends AuthProvider {
private URL url;
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
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
@ -66,4 +39,31 @@ else if (result.error != null)
public void close() {
// 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;
}
}
}

View file

@ -14,6 +14,9 @@
import java.util.Map;
public final class RejectAuthProvider extends AuthProvider implements Reconfigurable {
private String message;
private ArrayList<String> whitelist = new ArrayList<>();
public RejectAuthProvider() {
}
@ -21,9 +24,6 @@ public RejectAuthProvider(String message) {
this.message = message;
}
private String message;
private ArrayList<String> whitelist = new ArrayList<>();
@Override
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws AuthException {
if (whitelist != null) {

View file

@ -11,6 +11,10 @@
import java.util.UUID;
public final class RequestTextureProvider extends TextureProvider {
// Instance
private String skinURL;
private String cloakURL;
public RequestTextureProvider() {
}
@ -37,11 +41,6 @@ private static String getTextureURL(String url, UUID uuid, String username, Stri
"client", IOHelper.urlEncode(client == null ? "unknown" : client));
}
// Instance
private String skinURL;
private String cloakURL;
@Override
public void close() {
// Do nothing

View file

@ -55,7 +55,7 @@ public void replacePre(Predicate<LauncherBuildTask> pred, LauncherBuildTask task
}
public void replace(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
replaceCounted( 0, pred, taskRep);
replaceCounted(0, pred, taskRep);
}
public void replaceAfter(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {

View file

@ -37,6 +37,115 @@ public class BuildContext {
public final HashSet<String> fileList;
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 ZipOutputStream output;
private final Map<String, byte[]> hashs;
@ -60,7 +169,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String fileName = IOHelper.toString(sourceDir.relativize(file));
if(hashs != null)
if (hashs != null)
hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file));
// Create zip entry and transfer contents
@ -72,122 +181,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
}
private ZipEntry newEntry(String 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();
}
return newZipEntry(targetDir + IOHelper.CROSS_SEPARATOR + fileName);
}
}
}

View file

@ -36,7 +36,7 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
@Override
public void init() {
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));
if (server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
tasks.add(new ProGuardBuildTask(server));

View file

@ -19,17 +19,18 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
syncBinaryFile = binaryFile;
}
public void build() throws IOException
{
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
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);
}
public void build() throws IOException {
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
}
public final boolean exists() {
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
}
public final byte[] getDigest() {
return digest;
}
@ -48,8 +49,4 @@ public final boolean sync() throws IOException {
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);
}
}

View file

@ -14,27 +14,16 @@
import java.util.List;
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();
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 config;
public final Path mappings;
@ -49,6 +38,16 @@ public ProguardConf(LaunchServer 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) {
List<String> confStrs = new ArrayList<>();
prepare(false);
@ -57,11 +56,11 @@ public String[] buildConfig(Path inputJar, Path outputJar) {
confStrs.add("-obfuscationdictionary \'" + words.toFile().getName() + "\'");
confStrs.add("-injar \'" + inputJar.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()
.map(e -> "-libraryjars \'" + e.toAbsolutePath().toString() + "\'")
.forEach(confStrs::add);
srv.launcherBinary.addonLibs.stream()
.map(e -> "-libraryjars \'" + e.toAbsolutePath().toString() + "\'")
.forEach(confStrs::add);

View file

@ -40,22 +40,19 @@
*/
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_KEY_FN;
private static final String DIGEST_HASH = SignHelper.hashFunctionName + "-Digest";
private final ZipOutputStream zos;
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 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) {
zos = out;
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}
*/
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}
*/
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}
*/
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.
*
* @throws IOException
* @throws RuntimeException if the signing goes wrong
* @throws RuntimeException if the signing goes wrong
*/
@Override
public void close() throws IOException {
@ -152,7 +149,7 @@ public void close() throws IOException {
* underlying stream open.
*
* @throws IOException
* @throws RuntimeException if the signing goes wrong
* @throws RuntimeException if the signing goes wrong
*/
public void finish() throws IOException {
writeManifest();
@ -272,7 +269,7 @@ private byte[] writeSigFile() throws IOException {
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
*
* @throws IOException
* @throws RuntimeException if the signing failed
* @throws RuntimeException if the signing failed
*/
private void writeSignature(byte[] sigFile) throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(SIG_KEY_FN));

View file

@ -24,20 +24,6 @@ public AdditionalFixesApplyTask(LaunchServer 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 {
try (ClassMetadataReader reader = new ClassMetadataReader()) {
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));
if (filename.endsWith(".class")) {
byte[] bytes;
if(needFixes) {
if (needFixes) {
bytes = classFix(input, reader, srv.config.launcher.stripLineNumbers);
output.write(bytes);
}
else
} else
IOHelper.transfer(input, output);
} else
IOHelper.transfer(input, output);
@ -75,6 +60,20 @@ private static byte[] classFix(InputStream input, ClassMetadataReader reader, bo
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
public boolean allowDelete() {
return true;

View file

@ -3,7 +3,10 @@
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
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.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
@ -29,6 +32,9 @@
import java.util.Date;
public class CertificateAutogenTask implements LauncherBuildTask {
public X509Certificate certificate;
public X509CertificateHolder bcCertificate;
public CMSSignedDataGenerator signedDataGenerator;
private LaunchServer server;
public CertificateAutogenTask(LaunchServer server) {
@ -39,13 +45,10 @@ public CertificateAutogenTask(LaunchServer server) {
public String getName() {
return "CertificateAutogen";
}
public X509Certificate certificate;
public X509CertificateHolder bcCertificate;
public CMSSignedDataGenerator signedDataGenerator;
@Override
public Path process(Path inputFile) throws IOException {
if(signedDataGenerator != null) return inputFile;
if (signedDataGenerator != null) return inputFile;
try {
X500NameBuilder subject = new X500NameBuilder();
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");
ContentSigner signer = csBuilder.build(server.privateKey);
bcCertificate = builder.build(signer);
certificate = new JcaX509CertificateConverter().setProvider( "BC" )
.getCertificate( bcCertificate );
certificate = new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(bcCertificate);
ArrayList<Certificate> chain = new ArrayList<>();
chain.add(certificate);
signedDataGenerator = SignHelper.createSignedDataGenerator(server.privateKey, certificate, chain, "SHA256WITHECDSA");

View file

@ -27,94 +27,12 @@
import java.util.zip.ZipOutputStream;
public class MainBuildTask implements LauncherBuildTask {
private final LaunchServer server;
public final ClassMetadataReader reader;
@FunctionalInterface
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);
}
private final LaunchServer server;
public Set<String> blacklist = new HashSet<>();
public List<Transformer> transformers = new ArrayList<>();
public IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
public IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
public Map<String, Object> properties = new HashMap<>();
public MainBuildTask(LaunchServer srv) {
@ -163,7 +81,7 @@ public Path process(Path inputJar) throws IOException {
}
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 {
return e.getEncoded();
} catch (CertificateEncodingException e2) {
@ -171,8 +89,7 @@ protected void postInitProps() {
return new byte[0];
}
}).collect(Collectors.toList());
if(!server.config.sign.enabled)
{
if (!server.config.sign.enabled) {
CertificateAutogenTask task = server.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
try {
certificates.add(task.certificate.getEncoded());
@ -181,10 +98,10 @@ protected void postInitProps() {
}
}
properties.put("launchercore.certificates", certificates);
}
}
protected void initProps() {
properties.clear();
protected void initProps() {
properties.clear();
properties.put("launcher.address", server.config.netty.address);
properties.put("launcher.projectName", server.config.projectName);
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
@ -201,46 +118,38 @@ protected void initProps() {
//LogHelper.debug("[checkSecure] %s: %s", launcherSalt, Arrays.toString(launcherSecureHash));
if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
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;
ClassReader cr = null;
ClassWriter writer = null;
ClassNode cn = null;
for(Transformer t : transformers)
{
if(t instanceof ASMTransformer)
{
for (Transformer t : transformers) {
if (t instanceof ASMTransformer) {
ASMTransformer asmTransformer = (ASMTransformer) t;
if(cn == null)
{
if (cn == null) {
cr = new ClassReader(result);
cn = new ClassNode();
cr.accept(cn, 0);
}
asmTransformer.transform(cn, classname, context);
continue;
}
else if(cn != null)
{
} else if (cn != null) {
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(writer);
result = writer.toByteArray();
}
byte[] old_result = result;
result = t.transform(result, classname, context);
if(old_result != result)
{
if (old_result != result) {
cr = null;
cn = null;
}
}
if(cn != null)
{
writer = new SafeClassWriter(reader,ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
if (cn != null) {
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(writer);
result = writer.toByteArray();
}
@ -251,4 +160,84 @@ else if(cn != null)
public boolean allowDelete() {
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);
}
}

View file

@ -33,6 +33,17 @@ public SignJarTask(LaunchServerConfig.JarSignerConf config, LaunchServer 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
public String getName() {
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 {
if(config.enabled) stdSign(config, inputFile, signedFile);
if (config.enabled) stdSign(config, 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 {
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> {
CertificateAutogenTask task = srv.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
@ -95,15 +107,4 @@ private void autoSign(Path inputFile, Path signedFile) throws IOException {
public boolean allowDelete() {
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;
}
}
}

View file

@ -13,23 +13,10 @@
import java.nio.file.Path;
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
private static final String VERSION = Version.getVersion().getVersionString();
private static final int BUILD = Version.getVersion().build;
private final Path faviconFile;
private final LaunchServer server;
public Launch4JTask(LaunchServer launchServer) {
@ -37,6 +24,10 @@ public Launch4JTask(LaunchServer launchServer) {
faviconFile = launchServer.dir.resolve("favicon.ico");
}
public static String formatVars(String mask) {
return String.format(mask, VERSION, BUILD);
}
@Override
public String getName() {
return "launch4j";
@ -70,6 +61,7 @@ public Path process(Path inputFile) throws IOException {
public boolean allowDelete() {
return true;
}
private Path setConfig() {
Path path = server.launcherEXEBinary.nextPath(getName());
Config config = new Config();
@ -120,10 +112,17 @@ private Path setConfig() {
return path;
}
private static final String VERSION = Version.getVersion().getVersionString();
private static final int BUILD = Version.getVersion().build;
private final static class Launch4JLog extends Log {
private static final Launch4JLog INSTANCE = new Launch4JLog();
public static String formatVars(String mask) {
return String.format(mask, VERSION, BUILD);
@Override
public void append(String s) {
LogHelper.subInfo(s);
}
@Override
public void clear() {
// Do nothing
}
}
}

View file

@ -10,12 +10,12 @@
import java.security.KeyPair;
public class TestCommand extends Command {
private NettyServerSocketHandler handler = null;
public TestCommand(LaunchServer server) {
super(server);
}
private NettyServerSocketHandler handler = null;
@Override
public String getArgsDescription() {
return null;

View file

@ -20,65 +20,23 @@
import java.util.Collections;
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 OBJECTS_DIR = "objects";
private static final Gson gson = new Gson();
private static final String JSON_EXTENSION = ".json";
public IndexAssetCommand(LaunchServer server) {
super(server);
}
public static Path resolveIndexFile(Path assetDir, String name) {
return assetDir.resolve(INDEXES_DIR).resolve(name + JSON_EXTENSION);
}
public static Path resolveObjectFile(Path assetDir, String hash) {
return assetDir.resolve(OBJECTS_DIR).resolve(hash.substring(0, 2)).resolve(hash);
}
public IndexAssetCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[dir] [index] [output-dir]";
@ -122,4 +80,41 @@ public void invoke(String... args) throws Exception {
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
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);
}
}
}

View file

@ -19,6 +19,13 @@ public SaveProfilesCommand(LaunchServer 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
public String getArgsDescription() {
return "[profile names...]";
@ -32,19 +39,15 @@ public String getUsageDescription() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
if(args.length > 0)
{
for(String profileName : args)
{
if (args.length > 0) {
for (String profileName : args) {
Path profilePath = server.profilesDir.resolve(profileName.concat(".json"));
if(!Files.exists(profilePath))
{
if (!Files.exists(profilePath)) {
LogHelper.error("Profile %s not found", profilePath.toString());
return;
}
ClientProfile profile;
try(Reader reader = IOHelper.newReader(profilePath))
{
try (Reader reader = IOHelper.newReader(profilePath)) {
profile = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
}
saveProfile(profile, profilePath);
@ -53,12 +56,4 @@ public void invoke(String... args) throws Exception {
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);
}
}
}

View file

@ -12,26 +12,6 @@
import java.util.Optional;
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) {
super(server);
}
@ -50,11 +30,31 @@ public String getUsageDescription() {
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
Path targetDir = Paths.get(args[0]);
if(!IOHelper.isDir(targetDir))
if (!IOHelper.isDir(targetDir))
throw new IllegalArgumentException(String.format("%s not directory", targetDir.toString()));
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);
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);
}
}
}

View file

@ -30,16 +30,15 @@ public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
Path target = Paths.get(args[0]);
Path tmpSign;
if(args.length > 1)
if (args.length > 1)
tmpSign = Paths.get(args[1]);
else
tmpSign = server.dir.resolve("build").resolve(target.toFile().getName());
LogHelper.info("Signing jar %s to %s", target.toString(), tmpSign.toString());
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);
if(args.length <= 1)
{
if (args.length <= 1) {
LogHelper.info("Move temp jar %s to %s", tmpSign.toString(), target.toString());
Files.deleteIfExists(target);
Files.move(tmpSign, target);

View file

@ -12,9 +12,10 @@
import java.util.Map;
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 rateLimitMillis;
public final List<T> exclude = new ArrayList<>();
@Override
public Map<String, Command> getCommands() {
@ -67,23 +68,6 @@ public void garbageCollection() {
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) {
if (exclude.contains(address)) return true;
LimitEntry entry = map.get(address);
@ -105,4 +89,19 @@ public boolean check(T address) {
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;
}
}
}

View file

@ -7,6 +7,7 @@
import pro.gravit.utils.HookException;
public class AuthLimiterComponent extends IPLimiter implements NeedGarbageCollection, AutoCloseable {
public String message;
private transient LaunchServer srv;
@Override
@ -31,8 +32,6 @@ public boolean preAuthHook(AuthResponse.AuthContext context, Client client) {
return false;
}
public String message;
@Override
public void close() {
srv.authHookManager.preHook.unregisterHook(this::preAuthHook);

View file

@ -5,7 +5,6 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.handler.MemoryAuthHandler;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
@ -22,43 +21,109 @@
import pro.gravit.utils.helper.LogHelper;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
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 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) {
this.server = server;
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) {
return auth.get(name);
}
public ProtectHandler protectHandler;
public AuthProviderPair getAuthProviderPair() {
if (authDefault != null) return authDefault;
for (AuthProviderPair pair : auth.values()) {
@ -70,17 +135,6 @@ public AuthProviderPair getAuthProviderPair() {
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) {
this.projectName = projectName;
}
@ -93,7 +147,6 @@ public void setEnv(LauncherConfig.LauncherEnvironment env) {
this.env = env;
}
public void verify() {
if (auth == null || auth.size() < 1) {
throw new NullPointerException("AuthProviderPair`s count should be at least one");
@ -122,7 +175,7 @@ public void verify() {
public void init(LaunchServer.ReloadType type) {
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());
}
if (dao != null) {
@ -170,10 +223,9 @@ public void close(LaunchServer.ReloadType type) {
} catch (Exception e) {
LogHelper.error(e);
}
if(dao != null) {
if (dao != null) {
server.unregisterObject("dao", dao);
if(dao instanceof AutoCloseable)
{
if (dao instanceof AutoCloseable) {
try {
((AutoCloseable) dao).close();
} catch (Exception e) {
@ -264,73 +316,4 @@ public NettyBindAddress(String address, int 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;
}
}

View file

@ -6,14 +6,24 @@
public interface User {
String getUsername();
ClientPermissions getPermissions();
void setPermissions(ClientPermissions permissions);
boolean verifyPassword(String password);
void setPassword(String password);
String getAccessToken();
void setAccessToken(String accessToken);
String getServerID();
void setServerID(String serverID);
UUID getUuid();
void setUuid(UUID uuid);
}

View file

@ -15,21 +15,21 @@
@Entity(name = "User")
@Table(name = "users")
public class UserHibernateImpl implements User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(unique = true)
public String username;
public String email;
@Column(unique = true)
public UUID uuid;
public String serverID;
public long permissions;
public long flags;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "password")
private byte[] password;
private String accessToken;
public String serverID;
private String password_salt;
public long permissions;
public long flags;
public void setPassword(String password) {
password_salt = SecurityHelper.randomStringAESKey();

View file

@ -6,8 +6,8 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
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.UserHibernateImpl;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper;

View file

@ -27,12 +27,94 @@
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.
*/
public static class HashingOutputStream extends OutputStream {
public final OutputStream out;
public final MessageDigest hasher;
public final OutputStream out;
public final MessageDigest hasher;
public HashingOutputStream(OutputStream out, MessageDigest hasher) {
this.out = out;
@ -66,11 +148,12 @@ public void write(int b) throws IOException {
out.write(b);
hasher.update((byte) b);
}
public byte[] digest() {
return hasher.digest();
return hasher.digest();
}
}
/**
* Helper output stream that also sends the data to the given.
*/
@ -81,88 +164,7 @@ public HashingNonClosingOutputStream(OutputStream out, MessageDigest hasher) {
@Override
public void close() throws IOException {
// 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;
// Do nothing
}
}
}

View file

@ -20,11 +20,11 @@
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.launcher.LauncherTrustManager;
import java.io.*;
import java.math.BigInteger;
@ -48,17 +48,13 @@
import java.util.List;
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 minusHours = 6;
public X509CertificateHolder ca;
public AsymmetricKeyParameter caKey;
public X509CertificateHolder server;
public AsymmetricKeyParameter serverKey;
public LauncherTrustManager trustManager;
public String orgName;
public X509CertificateHolder generateCertificate(String subjectName, PublicKey subjectPublicKey) throws OperatorCreationException {

View file

@ -14,26 +14,6 @@
import java.util.Arrays;
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<>();
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");
}
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);
}
}
}

View file

@ -10,27 +10,6 @@
import java.util.Map;
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<>();
public void registerReconfigurable(String name, Reconfigurable reconfigurable) {
@ -59,4 +38,25 @@ public void printHelp(String name) throws CommandException {
public Map<String, Command> getCommands() {
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);
}
}
}

View file

@ -40,8 +40,7 @@ public Client getOrNewClient(long session) {
return clientSet.computeIfAbsent(session, Client::new);
}
public Client removeClient(long session)
{
public Client removeClient(long session) {
return clientSet.remove(session);
}

View file

@ -14,6 +14,7 @@ public class AuthHookManager {
public final BiHookSet<CheckServerResponse, Client> checkServerHook = new BiHookSet<>();
public final BiHookSet<JoinServerResponse, Client> joinServerHook = new BiHookSet<>();
public final BiHookSet<SetProfileResponse, Client> setProfileHook = new BiHookSet<>();
public final HookSet<RegContext> registraion = new HookSet<>();
public static class RegContext {
public final String login;
@ -28,6 +29,4 @@ public RegContext(String login, String password, String ip, boolean trustContext
this.trustContext = trustContext;
}
}
public final HookSet<RegContext> registraion = new HookSet<>();
}

View file

@ -1,11 +1,11 @@
package pro.gravit.launchserver.modules.impl;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.LauncherModuleInfo;
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.launcher.LauncherTrustManager;
import java.nio.file.Path;
import java.util.Arrays;

View file

@ -47,8 +47,8 @@ public enum Type {
SERVER,
USER
}
public static class TrustLevel
{
public static class TrustLevel {
public byte[] verifySecureKey;
public boolean keyChecked;
public byte[] publicKey;

View file

@ -25,12 +25,12 @@
import java.net.InetSocketAddress;
public class LauncherNettyServer implements AutoCloseable {
private static final String WEBSOCKET_PATH = "/api";
public final ServerBootstrap serverBootstrap;
public final EventLoopGroup bossGroup;
public final EventLoopGroup workerGroup;
public final WebSocketService service;
public final BiHookSet<NettyConnectContext, SocketChannel> pipelineHook = new BiHookSet<>();
private static final String WEBSOCKET_PATH = "/api";
public LauncherNettyServer(LaunchServer server) {
LaunchServerConfig.NettyConfig config = server.config.netty;

View file

@ -36,22 +36,11 @@
import java.lang.reflect.Type;
public class WebSocketService {
public final ChannelGroup channels;
public static final ProviderMap<WebSocketServerResponse> providers = new ProviderMap<>();
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 ChannelGroup channels;
public final BiHookSet<WebSocketRequestContext, ChannelHandlerContext> hook = new BiHookSet<>();
private final LaunchServer server;
private final Gson gson;
public WebSocketService(ChannelGroup channels, LaunchServer server) {
this.channels = channels;
@ -62,15 +51,36 @@ public WebSocketService(ChannelGroup channels, LaunchServer server) {
this.gson = Launcher.gsonManager.gson;
}
private final LaunchServer server;
private final Gson gson;
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);
providers.register("hardwareReport", HardwareReportResponse.class);
}
public void process(ChannelHandlerContext ctx, TextWebSocketFrame frame, Client client, String ip) {
String request = frame.text();
WebSocketServerResponse response = gson.fromJson(request, WebSocketServerResponse.class);
if(response == null)
{
RequestEvent event= new ErrorRequestEvent("This type of request is not supported");
if (response == null) {
RequestEvent event = new ErrorRequestEvent("This type of request is not supported");
sendObject(ctx, event);
}
process(ctx, response, client, ip);
@ -110,31 +120,6 @@ public void registerClient(Channel 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);
providers.register("hardwareReport", HardwareReportResponse.class);
}
public void sendObject(ChannelHandlerContext ctx, Object obj) {
ctx.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj, WebSocketEvent.class)), ctx.voidPromise());
}
@ -175,6 +160,18 @@ public void sendEvent(EventResult obj) {
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 EventResult() {

View file

@ -4,29 +4,30 @@
import java.nio.file.Files;
public enum ContentType {
NONE {
@Override
public String forPath(File p) {
return null;
}
},
NIO {
@Override
public String forPath(File p) {
try {
return Files.probeContentType(p.toPath());
} catch (Throwable e) {
return UNIVERSAL.forPath(p);
}
}
},
UNIVERSAL {
@Override
public String forPath(File p) {
return "application/octet-stream";
}
};
public abstract String forPath(File p);
NONE {
@Override
public String forPath(File p) {
return null;
}
},
NIO {
@Override
public String forPath(File p) {
try {
return Files.probeContentType(p.toPath());
} catch (Throwable e) {
return UNIVERSAL.forPath(p);
}
}
},
UNIVERSAL {
@Override
public String forPath(File p) {
return "application/octet-stream";
}
};
public abstract String forPath(File p);
}

View file

@ -7,21 +7,15 @@
import javax.net.ssl.SSLServerSocketFactory;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Set;
// TODO refactor
@SuppressWarnings("unused")
public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
private SSLServerSocketFactory ssf;
private transient final LaunchServer server;
public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections");
public LauncherNettyServer nettyServer;
// API
private Set<Socket> sockets;
private transient final LaunchServer server;
private SSLServerSocketFactory ssf;
public NettyServerSocketHandler(LaunchServer server) {
this.server = server;
@ -29,7 +23,9 @@ public NettyServerSocketHandler(LaunchServer server) {
@Override
public void close() {
//TODO: Close Impl
if (nettyServer == null) return;
nettyServer.close();
nettyServer.service.channels.close();
}
@Override

View file

@ -15,9 +15,14 @@
import java.util.concurrent.TimeUnit;
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
static {
}
public final LaunchServer srv;
public final WebSocketService service;
private final UUID connectUUID = UUID.randomUUID();
public NettyConnectContext context;
private Client client;
public WebSocketFrameHandler(NettyConnectContext context, LaunchServer srv, WebSocketService service) {
this.context = context;
@ -25,20 +30,14 @@ public WebSocketFrameHandler(NettyConnectContext context, LaunchServer srv, WebS
this.service = service;
}
private Client client;
private final UUID connectUUID = UUID.randomUUID();
static {
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public Client getClient() {
return client;
}
public final UUID getConnectUUID() {
return connectUUID;
}

View file

@ -28,13 +28,16 @@ public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpReque
public static final SimpleDateFormat dateFormatter;
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 {
dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
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 boolean fullOut;
private final boolean showHiddenFiles;
@ -45,6 +48,120 @@ public FileServerHandler(Path base, boolean fullOut, boolean 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
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
if (!request.decoderResult().isSuccess()) {
@ -154,120 +271,4 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
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);
}
}

View file

@ -2,7 +2,6 @@
import io.netty.channel.ChannelHandlerContext;
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.password.AuthECPassword;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
@ -22,7 +21,6 @@
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Random;
import java.util.UUID;
@ -37,13 +35,6 @@ public class AuthResponse extends SimpleResponse {
public String auth_id;
public ConnectTypes authType;
public enum ConnectTypes {
@Deprecated
SERVER,
CLIENT,
API
}
@Override
public String getType() {
return "auth";
@ -68,8 +59,7 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
AuthProviderPair pair;
if (auth_id.isEmpty()) pair = server.config.getAuthProviderPair();
else pair = server.config.getAuthProviderPair(auth_id);
if(pair == null)
{
if (pair == null) {
sendError("auth_id incorrect");
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 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) {
this.client = client;
this.login = login;
@ -125,13 +130,5 @@ public AuthContext(Client client, String login, String profileName, String ip, C
this.ip = ip;
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;
}
}

View file

@ -21,8 +21,7 @@ public String getType() {
@Override
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");
return;
}

View file

@ -11,6 +11,7 @@
public class ExitResponse extends SimpleResponse {
public boolean exitAll;
public String username;
@Override
public String getType() {
return "exit";
@ -18,66 +19,56 @@ public String getType() {
@Override
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");
return;
}
if(username == null)
{
if(client.session == 0 && exitAll)
{
if (username == null) {
if (client.session == 0 && exitAll) {
sendError("Session invalid");
return;
}
WebSocketFrameHandler handler = ctx.pipeline().get(WebSocketFrameHandler.class);
if(handler == null)
{
if (handler == null) {
sendError("Exit internal error");
return;
}
Client newClient = new Client(0);
newClient.checkSign = client.checkSign;
handler.setClient(newClient);
if(client.session != 0) server.sessionManager.removeClient(client.session);
if(exitAll)
{
if (client.session != 0) server.sessionManager.removeClient(client.session);
if (exitAll) {
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);
if(wsHandler == null || wsHandler == handler) return;
if (wsHandler == null || wsHandler == handler) return;
Client chClient = wsHandler.getClient();
if(client.isAuth && client.username != null)
{
if(!chClient.isAuth || !client.username.equals(chClient.username)) return;
}
else
{
if(chClient.session != client.session) return;
if (client.isAuth && client.username != null) {
if (!chClient.isAuth || !client.username.equals(chClient.username)) return;
} else {
if (chClient.session != client.session) return;
}
Client newCusClient = new Client(0);
newCusClient.checkSign = chClient.checkSign;
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);
event.requestUUID = RequestEvent.eventUUID;
wsHandler.service.sendObject(channel, event);
});
}
sendResult(new ExitRequestEvent(ExitRequestEvent.ExitReason.CLIENT));
}
else
{
} else {
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);
if(wsHandler == null) return;
if (wsHandler == null) return;
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);
newCusClient.checkSign = chClient.checkSign;
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);
event.requestUUID = RequestEvent.eventUUID;
wsHandler.service.sendObject(channel, event);

View file

@ -20,8 +20,7 @@ public String getType() {
@Override
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");
return;
}

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver.socket.response.auth;
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.ErrorRequestEvent;
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
@ -26,20 +25,15 @@ public void execute(ChannelHandlerContext ctx, Client client) {
List<ClientProfile> profileList;
List<ClientProfile> serverProfiles = server.getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler)
{
if (server.config.protectHandler instanceof ProfilesProtectHandler) {
ProfilesProtectHandler protectHandler = (ProfilesProtectHandler) server.config.protectHandler;
profileList = new ArrayList<>(4);
for(ClientProfile profile : serverProfiles)
{
if(protectHandler.canGetProfile(profile, client))
{
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
}
else
{
} else {
profileList = serverProfiles;
}
sendResult(new ProfilesRequestEvent(profileList));

View file

@ -19,6 +19,12 @@ public class RegisterResponse extends SimpleResponse {
public String email;
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
public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
byte[] normalHash = registerHash(login, server.runtime.registerApiKey);
@ -43,10 +49,4 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
public String getType() {
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));
}
}

View file

@ -10,11 +10,6 @@
import java.util.UUID;
public class BatchProfileByUsername extends SimpleResponse {
static class Entry {
String username;
String client;
}
Entry[] list;
@Override
@ -36,4 +31,9 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
}
sendResult(result);
}
static class Entry {
String username;
String client;
}
}

View file

@ -14,21 +14,20 @@ public String getType() {
@Override
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);
response.enabled = false;
sendResult(response);
return;
}
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
if(!secureProtectHandler.allowGetSecureLevelInfo(client))
{
if (!secureProtectHandler.allowGetSecureLevelInfo(client)) {
sendError("Access denied");
return;
}
if(client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
if(client.trustLevel.verifySecureKey == null) client.trustLevel.verifySecureKey = secureProtectHandler.generateSecureLevelKey();
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
if (client.trustLevel.verifySecureKey == null)
client.trustLevel.verifySecureKey = secureProtectHandler.generateSecureLevelKey();
GetSecureLevelInfoRequestEvent response = new GetSecureLevelInfoRequestEvent(client.trustLevel.verifySecureKey);
response.enabled = true;
sendResult(secureProtectHandler.onGetSecureLevelInfo(response));

View file

@ -13,6 +13,7 @@ public class SecurityReportResponse extends SimpleResponse {
public String largeData;
public byte[] smallBytes;
public byte[] largeBytes;
@Override
public String getType() {
return "securityReport";
@ -20,8 +21,7 @@ public String getType() {
@Override
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");
}
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;

View file

@ -12,6 +12,7 @@
public class VerifySecureLevelKeyResponse extends SimpleResponse {
public byte[] publicKey;
public byte[] signature;
@Override
public String getType() {
return "verifySecureLevelKey";
@ -19,24 +20,20 @@ public String getType() {
@Override
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");
return;
}
SecureProtectHandler secureProtectHandler = (SecureProtectHandler) server.config.protectHandler;
try {
secureProtectHandler.verifySecureLevelKey(publicKey, client.trustLevel.verifySecureKey, signature);
} catch (InvalidKeySpecException e)
{
} catch (InvalidKeySpecException e) {
sendError("Invalid public key");
return;
} catch (SignatureException e)
{
} catch (SignatureException e) {
sendError("Invalid signature");
return;
} catch (SecurityException e)
{
} catch (SecurityException e) {
sendError(e.getMessage());
return;
}

View file

@ -3,12 +3,10 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.UpdateRequestEvent;
import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.IOHelper;
public class UpdateResponse extends SimpleResponse {

View file

@ -12,40 +12,21 @@
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 class TestClass
{
@LauncherInject(value = "testprop")
public int test;
@LauncherInject(value = "testprop2")
public List<String> s;
@LauncherInject(value = "testprop3")
public Map<String, String> map;
}
@BeforeAll
public static void prepare() throws Throwable {
classLoader = new ASMClassLoader(ASMTransformersTest.class.getClassLoader());
}
@Test
void testASM() throws Throwable
{
void testASM() throws Throwable {
ClassReader reader = new ClassReader(JarHelper.getClassBytes(TestClass.class));
ClassNode node = new ClassNode();
reader.accept(node, ClassReader.SKIP_DEBUG);
@ -69,11 +50,30 @@ void testASM() throws Throwable
Class<?> clazz = classLoader.loadClass("ASMTestClass");
Object instance = MethodHandles.publicLookup().findConstructor(clazz, MethodType.methodType(void.class)).invoke();
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>)
MethodHandles.publicLookup().findGetter(clazz, "s", List.class).invoke(instance));
Assertions.assertEquals(byteMap, (Map<String, Object>)
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;
}
}

View file

@ -1,5 +1,6 @@
package pro.gravit.launchserver;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@ -28,7 +29,7 @@ public class StartLaunchServerTest {
public static LaunchServer launchServer;
@BeforeAll
public static void prepare() throws Exception {
public static void prepare() throws Throwable {
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(modulesDir, configDir, null);
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
@ -48,12 +49,14 @@ public static void prepare() throws Exception {
.setLaunchServerConfigManager(new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() {
return null;
return LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
return null;
LaunchServerRuntimeConfig r = new LaunchServerRuntimeConfig();
r.reset();
return r;
}
@Override
@ -71,6 +74,11 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
launchServer = builder.build();
}
@AfterAll
public static void complete() throws Throwable {
launchServer.close();
}
@Test
public void start() {
launchServer.run();

View file

@ -11,7 +11,7 @@
}
javafx {
version = "12"
modules = [ 'javafx.controls', 'javafx.fxml' ]
modules = ['javafx.controls', 'javafx.fxml']
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'

View file

@ -19,8 +19,8 @@ public class ClientLauncherWrapper {
public static final String MAGIC_ARG = "-Djdk.attach.allowAttachSelf";
public static final String WAIT_PROCESS_PROPERTY = "launcher.waitProcess";
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 boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY);
public static void main(String[] arguments) throws IOException, InterruptedException {
LogHelper.printVersion("Launcher");
@ -76,7 +76,7 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
if (!modulePath.isEmpty()) {
args.add("--add-modules");
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("--module-path");
args.add(modulePath);

View file

@ -8,11 +8,11 @@ public abstract class JSApplication extends Application {
private static final AtomicReference<JSApplication> INSTANCE = new AtomicReference<>();
public static JSApplication getInstance() {
return INSTANCE.get();
}
public JSApplication() {
INSTANCE.set(this);
}
public static JSApplication getInstance() {
return INSTANCE.get();
}
}

View file

@ -12,8 +12,8 @@
public final class LauncherAgent {
private static boolean isAgentStarted = false;
public static Instrumentation inst;
private static boolean isAgentStarted = false;
public static void addJVMClassPath(String path) throws IOException {
LogHelper.debug("Launcher Agent addJVMClassPath");
@ -25,10 +25,6 @@ public static void addJVMClassPath(Path path) throws IOException {
inst.appendToSystemClassLoaderSearch(new JarFile(path.toFile()));
}
public boolean isAgentStarted() {
return isAgentStarted;
}
public static void premain(String agentArgument, Instrumentation instrumentation) {
System.out.println("Launcher Agent");
checkAgentStacktrace();
@ -58,4 +54,8 @@ public static void checkAgentStacktrace() {
public static boolean isStarted() {
return isAgentStarted;
}
public boolean isAgentStarted() {
return isAgentStarted;
}
}

View file

@ -1,6 +1,9 @@
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.ClientExitPhase;
import pro.gravit.launcher.client.events.ClientPreGuiPhase;
@ -33,6 +36,20 @@
import java.util.concurrent.atomic.AtomicBoolean;
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
public static X509Certificate[] getCertificates(Class<?> clazz) {
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);
}
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 {
LauncherTrustManager trustManager = Launcher.getConfig().trustManager;
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));
try {
System.exit(code);
@ -108,7 +120,22 @@ public static void initGson(ClientModuleManager modulesManager) {
}
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 {
@ -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 {
//Launcher.modulesManager = new ClientModuleManager(this);
LauncherEngine.guard = tryGetStdGuard();
@ -188,20 +201,4 @@ public void start(String... args) throws Throwable {
LogHelper.debug("Dir: %s", DirBridge.dir);
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();
}
}

View file

@ -4,9 +4,14 @@
import pro.gravit.launcher.hasher.HashedDir;
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 {
@LauncherNetworkAPI
public final transient List<HashedStoreEntry> lastHDirs = new ArrayList<>(16);
@LauncherNetworkAPI
public Map<String, UserSettings> userSettings = new HashMap<>();
@LauncherNetworkAPI
@ -14,6 +19,14 @@ public class NewLauncherSettings {
@LauncherNetworkAPI
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 final HashedDir hdir;
public final String name;
@ -26,15 +39,4 @@ public HashedStoreEntry(HashedDir hdir, String name, String 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);
}
}

View file

@ -8,16 +8,16 @@ public class SystemService {
private SystemService() {
throw new UnsupportedOperationException();
}
public static void exit(int code)
{
public static void exit(int 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());
if(AuthService.profile == null || AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.NONE) return;
if(AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.CLIENT)
{
if (AuthService.profile == null || AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.NONE)
return;
if (AuthService.profile.securityManagerConfig == ClientProfile.SecurityManagerConfig.CLIENT) {
System.setSecurityManager(s);
}
//TODO NEXT

View file

@ -62,21 +62,21 @@ public ClientClassLoader(URL[] urls, ClassLoader parent) {
public String findLibrary(String name) {
return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(getNativePrefix()).concat(name).concat(getNativeEx());
}
public String getNativeEx()
{
if(JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
public String getNativeEx() {
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
return ".dll";
else if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
else if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
return ".so";
else if(JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
else if (JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
return ".dylib";
return "";
}
public String getNativePrefix()
{
if(JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
public String getNativePrefix() {
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
return "lib";
else if(JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
else if (JVMHelper.OS_TYPE == JVMHelper.OS.MACOSX)
return "lib";
return "";
}

View file

@ -26,7 +26,10 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
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.Path;
import java.nio.file.Paths;
@ -37,12 +40,12 @@
import java.util.stream.Stream;
public class ClientLauncherEntryPoint {
private static ClientClassLoader classLoader;
private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException {
try (Socket socket = IOHelper.newSocket())
{
try (Socket socket = IOHelper.newSocket()) {
socket.connect(address);
try(HInput input = new HInput(socket.getInputStream()))
{
try (HInput input = new HInput(socket.getInputStream())) {
byte[] serialized = input.readByteArray(0);
ClientLauncherProcess.ClientParams params = Launcher.gsonManager.gson.fromJson(new String(serialized, IOHelper.UNICODE_CHARSET), ClientLauncherProcess.ClientParams.class);
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 {
LauncherEngine.IS_CLIENT.set(true);
LauncherEngine engine = LauncherEngine.clientInstance();
@ -147,10 +150,12 @@ public static void main(String[] args) throws Throwable {
launch(profile, params);
}
}
private static void initGson(ClientModuleManager moduleManager) {
Launcher.gsonManager = new ClientGsonManager(moduleManager);
Launcher.gsonManager.initGson();
}
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
//if (matcher != null)
// 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)));
}
}
public static void checkJVMBitsAndVersion() {
if (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);
}
}
private static LinkedList<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException {
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();
}
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 {
// Add client args
Collection<String> args = new LinkedList<>();
@ -239,8 +233,7 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa
LogHelper.debug("Args: " + copy);
// Resolve main class and method
Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
for(URL u : classLoader.getURLs())
{
for (URL u : classLoader.getURLs()) {
LogHelper.info("ClassLoader URL: %s", u.toString());
}
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);
}
}
}

View file

@ -24,18 +24,18 @@
import java.util.*;
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 List<String> jvmArgs = new LinkedList<>();
public final List<String> systemClientArgs = new LinkedList<>();
public final List<String> systemClassPath = new LinkedList<>();
public final Map<String, String> systemEnv = new HashMap<>();
public final String mainClass;
private final transient Boolean[] waitWriteParams = new Boolean[]{false};
public Path executeFile;
public Path workDir;
public Path javaDir;
public boolean isStarted;
private transient Process process;
public ClientLauncherProcess(Path executeFile, Path workDir, Path javaDir, String mainClass) {
this.executeFile = executeFile;
@ -74,8 +74,15 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir, Path r
this.params.javaHDir = jvmHDir;
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());
Collections.addAll(this.jvmArgs, this.params.profile.getJvmArgs());
this.params.profile.pushOptionalJvmArgs(this.jvmArgs);
@ -89,9 +96,64 @@ private void applyClientProfile()
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);
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 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 clientDir;
@ -128,27 +190,13 @@ public static class ClientParams
public transient HashedDir javaHDir;
public void addClientArgs(Collection<String> args)
{
public void addClientArgs(Collection<String> args) {
if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0)
addModernClientArgs(args);
else
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) {
args.add(playerProfile.username);
args.add(accessToken);
@ -211,74 +259,16 @@ private void addModernClientArgs(Collection<String> args) {
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() {
return process;
}
public static String getPathSeparator()
{
if(JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
return ";";
else
return ":";
public static class ClientUserProperties {
@LauncherNetworkAPI
public String[] skinURL;
@LauncherNetworkAPI
public String[] skinDigest;
@LauncherNetworkAPI
public String[] cloakURL;
@LauncherNetworkAPI
public String[] cloakDigest;
}
}
}

View file

@ -1,9 +1,9 @@
package pro.gravit.launcher.client;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
import pro.gravit.launcher.LauncherTrustManager;
import java.nio.file.Path;
import java.util.Collection;

View file

@ -29,13 +29,27 @@ public class DirBridge {
public static boolean useLegacyDir;
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);
}
}
public static void move(Path newDir) throws IOException {
IOHelper.move(dirUpdates, newDir);
dirUpdates = newDir;
}
public static Path getAppDataDir() throws IOException {
boolean isCustomDir = Boolean.getBoolean(System.getProperty(USE_CUSTOMDIR_PROPERTY, "false"));
if (isCustomDir) {
@ -65,12 +79,10 @@ public static Path getAppDataDir() throws IOException {
}
}
public static Path getLauncherDir(String projectname) throws IOException {
return getAppDataDir().resolve(projectname);
}
public static Path getStoreDir(String projectname) throws IOException {
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX)
return getAppDataDir().resolve("store");
@ -80,39 +92,19 @@ else if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE)
return getAppDataDir().resolve("minecraftStore");
}
public static Path getProjectStoreDir(String projectname) throws IOException {
return getStoreDir(projectname).resolve(projectname);
}
public static Path getGuardDir() {
return dir.resolve("guard");
}
public static Path getLegacyLauncherDir(String projectname) {
return IOHelper.HOME_DIR.resolve(projectname);
}
public static void setUseLegacyDir(boolean 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);
}
}
}

View file

@ -21,34 +21,24 @@
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
private static final String LEGACY_PING_HOST_MAGIC = "§1";
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 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 {
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));
}
// 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 {
try (Socket socket = IOHelper.newSocket()) {
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);
}
public Result ping() throws IOException {
Instant now = Instant.now();
synchronized (cacheLock) {
@ -222,4 +193,26 @@ public Result ping() throws IOException {
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;
}
}
}

View file

@ -3,7 +3,6 @@
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.client.ClientLauncherProcess;
import pro.gravit.launcher.modules.events.PostInitPhase;
import pro.gravit.launcher.profiles.ClientProfile;
public class ClientProcessReadyEvent extends PostInitPhase {
public final LauncherEngine clientInstance;

View file

@ -21,8 +21,7 @@ public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
if (ConsoleManager.checkUnlockKey(args[0])) {
LogHelper.info("Unlock successful");
if(!ConsoleManager.unlock())
{
if (!ConsoleManager.unlock()) {
LogHelper.error("Console unlock canceled");
return;
}

View file

@ -1,10 +1,5 @@
package pro.gravit.launcher.guard;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import java.nio.file.Path;
public class LauncherGuardManager {
public static LauncherGuardInterface guard;

View file

@ -10,6 +10,19 @@
public class LauncherWrapperGuard implements LauncherGuardInterface {
public LauncherWrapperGuard() {
try {
String wrapperName = JVMHelper.JVM_BITS == 64 ? "wrapper64.exe" : "wrapper32.exe";
String projectName = Launcher.getConfig().projectName;
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
String antiInjectName = JVMHelper.JVM_BITS == 64 ? "AntiInject64.dll" : "AntiInject32.dll";
UnpackHelper.unpack(Launcher.getResourceURL(wrapperName, "guard"), DirBridge.getGuardDir().resolve(wrapperUnpackName));
UnpackHelper.unpack(Launcher.getResourceURL(antiInjectName, "guard"), DirBridge.getGuardDir().resolve(antiInjectName));
} catch (IOException e) {
throw new SecurityException(e);
}
}
@Override
public String getName() {
return "wrapper";
@ -23,17 +36,4 @@ public void applyGuardParams(ClientLauncherProcess process) {
process.executeFile = DirBridge.getGuardDir().resolve(wrapperUnpackName);
}
}
public LauncherWrapperGuard() {
try {
String wrapperName = JVMHelper.JVM_BITS == 64 ? "wrapper64.exe" : "wrapper32.exe";
String projectName = Launcher.getConfig().projectName;
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
String antiInjectName = JVMHelper.JVM_BITS == 64 ? "AntiInject64.dll" : "AntiInject32.dll";
UnpackHelper.unpack(Launcher.getResourceURL(wrapperName, "guard"), DirBridge.getGuardDir().resolve(wrapperUnpackName));
UnpackHelper.unpack(Launcher.getResourceURL(antiInjectName, "guard"), DirBridge.getGuardDir().resolve(antiInjectName));
} catch (IOException e) {
throw new SecurityException(e);
}
}
}

View file

@ -51,10 +51,10 @@ public static boolean checkUnlockKey(String key) {
}
public static boolean unlock() {
if(isConsoleUnlock) return true;
if (isConsoleUnlock) return true;
ClientUnlockConsoleEvent event = new ClientUnlockConsoleEvent(handler);
LauncherEngine.modulesManager.invokeEvent(event);
if(event.isCancel()) return false;
if (event.isCancel()) return false;
handler.registerCommand("debug", new DebugCommand());
handler.unregisterCommand("unlock");
isConsoleUnlock = true;

View file

@ -10,10 +10,68 @@
import pro.gravit.utils.helper.LogHelper;
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;
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> {
@Override
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);
}
}

View file

@ -16,9 +16,13 @@
public class FMLPatcher extends ClassLoader implements Opcodes {
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 Vector<MethodHandle> MHS = new Vector<>();
public static volatile FMLPatcher INSTANCE = null;
public FMLPatcher(final ClassLoader cl) {
super(cl);
}
public static void apply() {
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();
}
public FMLPatcher(final ClassLoader cl) {
super(cl);
}
public Class<?> def(final String name, final String exName) {
return super.defineClass(name, ByteBuffer.wrap(gen(name.replace('.', '/'), exName)), null);
}

View file

@ -18,60 +18,20 @@
import java.util.Objects;
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 String IGN_OVERFLOW = "launcher.dirwatcher.ignoreOverflows";
// Constants
private static final Kind<?>[] KINDS = {
StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE
};
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;
}
private static final boolean PROP_IGN_OVERFLOW = Boolean.parseBoolean(System.getProperty(IGN_OVERFLOW, "true"));
// Instance
private final Path dir;
private final HashedDir hdir;
private final FileNameMatcher matcher;
private final WatchService service;
private final boolean digest;
public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
this.dir = Objects.requireNonNull(dir, "dir");
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());
}
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
public void close() throws IOException {
service.close();
@ -136,4 +108,25 @@ public void run() {
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;
}
}
}

View file

@ -8,20 +8,13 @@
import java.awt.event.WindowEvent;
public final class NativeJVMHalt {
public final int haltCode;
public NativeJVMHalt(int haltCode) {
this.haltCode = haltCode;
System.out.println("JVM exit code " + haltCode);
}
public final int haltCode;
public native void aaabbb38C_D();
@SuppressWarnings("null")
private boolean aaabBooleanC_D() {
return (boolean) (Boolean) null;
}
public static void haltA(int code) {
Throwable[] th = new Throwable[3];
NativeJVMHalt halt = new NativeJVMHalt(code);
@ -40,9 +33,8 @@ public static void haltA(int code) {
} catch (Throwable fmlExc) {
th[2] = fmlExc;
}
for(Throwable t : th)
{
if(t != null) LogHelper.error(t);
for (Throwable t : th) {
if (t != null) LogHelper.error(t);
}
boolean a = halt.aaabBooleanC_D();
System.out.println(a);
@ -54,6 +46,13 @@ public static boolean initFunc() {
return true;
}
public native void aaabbb38C_D();
@SuppressWarnings("null")
private boolean aaabBooleanC_D() {
return (boolean) (Boolean) null;
}
public static class WindowShutdown extends JFrame {
private static final long serialVersionUID = 6321323663070818367L;

View file

@ -4,34 +4,11 @@
import pro.gravit.launcher.serialize.HOutput;
import java.io.IOException;
import java.util.StringJoiner;
public class ClientPermissions {
public static final ClientPermissions DEFAULT = new ClientPermissions();
public long permissions;
public long flags;
public enum PermissionConsts
{
ADMIN(0x01),
MANAGEMENT(0x02);
public final long mask;
PermissionConsts(long mask) {
this.mask = mask;
}
}
public enum FlagConsts
{
SYSTEM(0x01),
BANNED(0x02),
UNTRUSTED(0x04),
HIDDEN(0x08);
public final long mask;
FlagConsts(long mask) {
this.mask = mask;
}
}
public ClientPermissions(HInput input) throws IOException {
this(input.readLong());
@ -50,62 +27,83 @@ public ClientPermissions(long permissions, long flags) {
this.flags = flags;
}
public long toLong() {
return permissions;
}
public static ClientPermissions getSuperuserAccount() {
ClientPermissions perm = new ClientPermissions();
return perm;
}
public long toLong() {
return permissions;
}
@Deprecated
public void write(HOutput output) throws IOException {
output.writeLong(toLong());
}
//Read methods
public final boolean isPermission(PermissionConsts con)
{
public final boolean isPermission(PermissionConsts con) {
return (permissions & con.mask) != 0;
}
public final boolean isPermission(long mask)
{
public final boolean isPermission(long mask) {
return (permissions & mask) != 0;
}
public final boolean isFlag(FlagConsts con)
{
public final boolean isFlag(FlagConsts con) {
return (flags & con.mask) != 0;
}
public final boolean isFlag(long mask)
{
public final boolean isFlag(long mask) {
return (flags & mask) != 0;
}
//Write methods
public final void setPermission(PermissionConsts con, boolean value)
{
if(value) this.permissions |= con.mask;
public final void setPermission(PermissionConsts con, boolean value) {
if (value) this.permissions |= con.mask;
else this.permissions &= ~con.mask;
}
public final void setPermission(long mask, boolean value)
{
if(value) this.permissions |= mask;
public final void setPermission(long mask, boolean value) {
if (value) this.permissions |= mask;
else this.permissions &= ~mask;
}
public final void setFlag(FlagConsts con, boolean value)
{
if(value) this.flags |= con.mask;
public final void setFlag(FlagConsts con, boolean value) {
if (value) this.flags |= con.mask;
else this.flags &= ~con.mask;
}
public final void setFlag(long mask, boolean value)
{
if(value) this.flags |= mask;
public final void setFlag(long mask, boolean value) {
if (value) this.flags |= mask;
else this.flags &= ~mask;
}
@Override
public String toString() {
return String.format("permissions %d | flags %d", permissions, flags);
}
public enum PermissionConsts {
ADMIN(0x01),
MANAGEMENT(0x02);
public final long mask;
PermissionConsts(long mask) {
this.mask = mask;
}
}
public enum FlagConsts {
SYSTEM(0x01),
BANNED(0x02),
UNTRUSTED(0x04),
HIDDEN(0x08);
public final long mask;
FlagConsts(long mask) {
this.mask = mask;
}
}
}

View file

@ -30,29 +30,19 @@ public final class Launcher {
public static final String CLOAK_DIGEST_PROPERTY = "cloakDigest";
// Used to determine from clientside is launched from launcher
public static final AtomicBoolean LAUNCHED = new AtomicBoolean(false);
private static final AtomicReference<LauncherConfig> CONFIG = new AtomicReference<>();
public static final int PROTOCOL_MAGIC_LEGACY = 0x724724_00 + 24;
public static final int PROTOCOL_MAGIC = 0xA205B064; // e = 2.718281828
// Constants
public static final String RUNTIME_DIR = "runtime";
// Constants
public static final String GUARD_DIR = "guard";
public static final String CONFIG_FILE = "config.bin";
public static ClientProfile profile;
public static final String INIT_SCRIPT_FILE = "init.js";
public static final String API_SCRIPT_FILE = "engine/api.js";
public static final String CONFIG_SCRIPT_FILE = "config.js";
private static final AtomicReference<LauncherConfig> CONFIG = new AtomicReference<>();
private static final Pattern UUID_PATTERN = Pattern.compile("-", Pattern.LITERAL);
public static ClientProfile profile;
public static GsonManager gsonManager;

View file

@ -18,30 +18,20 @@
import java.util.*;
public final class LauncherConfig extends StreamObject {
@LauncherInject("launchercore.certificates")
private static final List<byte[]> secureConfigCertificates = null;
@LauncherInject("launcher.modules")
private static final List<Class<?>> modulesClasses = null;
@LauncherInject("launcher.address")
public String address;
@LauncherInject("launcher.projectName")
@LauncherInject("launchercore.certificates")
private static final List<byte[]> secureConfigCertificates = null;
@LauncherInject("launcher.modules")
private static final List<Class<?>> modulesClasses = null;
private static final MethodType VOID_TYPE = MethodType.methodType(void.class);
@LauncherInject("launcher.projectName")
public final String projectName;
@LauncherInject("launcher.port")
@LauncherInject("launcher.port")
public final int clientPort;
@LauncherInject("runtimeconfig.secretKeyClient")
public String secretKeyClient;
@LauncherInject("runtimeconfig.oemUnlockKey")
public String oemUnlockKey;
public final LauncherTrustManager trustManager;
public final ECPublicKey publicKey;
public final Map<String, byte[]> runtime;
@LauncherInject("launcher.isWarningMissArchJava")
public final boolean isWarningMissArchJava;
@LauncherInject("launchercore.env")
public LauncherEnvironment environment;
@LauncherInject("launcher.guardType")
public final String guardType;
@LauncherInject("runtimeconfig.secureCheckHash")
@ -50,8 +40,17 @@ public final class LauncherConfig extends StreamObject {
public final String secureCheckSalt;
@LauncherInject("runtimeconfig.passwordEncryptKey")
public final String passwordEncryptKey;
@LauncherInject("launcher.address")
public String address;
@LauncherInject("runtimeconfig.secretKeyClient")
public String secretKeyClient;
@LauncherInject("runtimeconfig.oemUnlockKey")
public String oemUnlockKey;
@LauncherInject("launchercore.env")
public LauncherEnvironment environment;
@LauncherInjectionConstructor
@LauncherInjectionConstructor
public LauncherConfig(HInput input) throws IOException, InvalidKeySpecException {
publicKey = SecurityHelper.toPublicECKey(input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH));
secureCheckHash = null;
@ -84,7 +83,6 @@ public LauncherConfig(HInput input) throws IOException, InvalidKeySpecException
runtime = Collections.unmodifiableMap(localResources);
}
public LauncherConfig(String address, ECPublicKey publicKey, Map<String, byte[]> runtime, String projectName) {
this.address = address;
this.publicKey = publicKey;
@ -100,6 +98,17 @@ public LauncherConfig(String address, ECPublicKey publicKey, Map<String, byte[]>
trustManager = null;
}
public static void initModules(LauncherModulesManager modulesManager) {
for (Class<?> clazz : modulesClasses)
try {
modulesManager.loadModule((LauncherModule) MethodHandles.publicLookup().findConstructor(clazz, VOID_TYPE).invokeWithArguments(Collections.emptyList()));
} catch (Throwable e) {
LogHelper.error(e);
}
// This method should be called once at exec time.
modulesClasses.clear();
}
@Override
public void write(HOutput output) throws IOException {
output.writeByteArray(publicKey.getEncoded(), SecurityHelper.CRYPTO_MAX_LENGTH);
@ -116,17 +125,4 @@ public void write(HOutput output) throws IOException {
public enum LauncherEnvironment {
DEV, DEBUG, STD, PROD
}
private static final MethodType VOID_TYPE = MethodType.methodType(void.class);
public static void initModules(LauncherModulesManager modulesManager) {
for (Class<?> clazz : modulesClasses)
try {
modulesManager.loadModule((LauncherModule) MethodHandles.publicLookup().findConstructor(clazz, VOID_TYPE).invokeWithArguments(Collections.emptyList()));
} catch (Throwable e) {
LogHelper.error(e);
}
// This method should be called once at exec time.
modulesClasses.clear();
}
}

View file

@ -4,8 +4,8 @@
import java.nio.file.Path;
public abstract class JsonConfigurable<T> implements JsonConfigurableInterface<T> {
private transient final Type type;
protected transient final Path configPath;
private transient final Type type;
public JsonConfigurable(Type type, Path configPath) {
@ -26,9 +26,7 @@ public Type getType() {
public abstract T getConfig();
public abstract void setConfig(T config);
public abstract T getDefaultConfig();
public abstract void setConfig(T config);
}

View file

@ -29,13 +29,11 @@ default void saveConfig(Gson gson, Path configPath) throws IOException {
}
}
default String toJsonString(Gson gson)
{
default String toJsonString(Gson gson) {
return gson.toJson(getConfig(), getType());
}
default String toJsonString()
{
default String toJsonString() {
return toJsonString(Launcher.gsonManager.configGson);
}
@ -91,12 +89,9 @@ default boolean generateConfigIfNotExists() throws IOException {
T getConfig();
T getDefaultConfig();
void setConfig(T config);
T getDefaultConfig();
Path getPath();

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