Merge branch 'release/5.0.9'

This commit is contained in:
Gravit 2019-09-27 06:42:58 +07:00
commit 610b6de76f
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
39 changed files with 674 additions and 296 deletions

View file

@ -1,41 +1,49 @@
image: frekele/java
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
CI_VERSION: '6.6.$CI_PIPELINE_IID'
stages:
- build
- test
- deploy
before_script:
# - echo `pwd` # debug
# - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug
- export GRADLE_USER_HOME=`pwd`/.gradle
- apt-get update -qq && apt-get install -y -qq git git-core
cache:
key: ${CI_COMMIT_REF_NAME}
paths:
- .gradle/wrapper
- .gradle/caches
build:
image: frekele/java
stage: build
script:
before_script:
- apt-get -y update
- apt-get -y install zip git
- export GRADLE_USER_HOME=`pwd`/.gradle
- chmod +x gradlew
- sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
- git submodule sync
- git submodule update --init --recursive
script:
- ./gradlew assemble
- mv LaunchServer/build/libs/*.jar LaunchServer
- mv ServerWrapper/build/libs/*.jar ServerWrapper
- mv modules/*_module/build/libs/*.jar modules
artifacts:
after_script:
- mkdir -p artifacts/modules
- cd LaunchServer/build/libs/
- zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
- mv LaunchServer.jar ../../../artifacts/LaunchServer.jar
- cd ../../../ServerWrapper/build/libs
- mv ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
- cd ../../../
- mv modules/*_module/build/libs/*.jar artifacts/modules
- mv modules/*_swmodule/build/libs/*.jar artifacts/modules
- mv modules/*_lmodule/build/libs/*.jar artifacts/modules
cache:
paths:
- LaunchServer/*.jar
- ServerWrapper/*.jar
- modules/*.jar
- .gradle
artifacts:
expire_in: 6 week
paths:
- artifacts
test:
image: frekele/java
stage: test
script:
- ./gradlew check
after_script:
- echo "End CI"

View file

@ -56,6 +56,15 @@ public void acceptVisitor(String className, ClassVisitor visitor) throws IOExcep
acceptVisitor(getClassData(className), visitor);
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor, int flags) {
new ClassReader(classData).accept(visitor, flags);
}
public void acceptVisitor(String className, ClassVisitor visitor, int flags) throws IOException {
acceptVisitor(getClassData(className), visitor, flags);
}
public byte[] getClassData(String className) throws IOException {
for (JarFile f : cp) {
if (f.getEntry(className + ".class") != null) {

View file

@ -0,0 +1,169 @@
package pro.gravit.launchserver.asm;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import pro.gravit.utils.helper.IOHelper;
import static org.objectweb.asm.Opcodes.*;
public final class NodeUtils {
private NodeUtils() { }
public static ClassNode forClass(Class<?> cls, int flags) {
try (InputStream in = cls.getClassLoader().getResourceAsStream(cls.getName().replace('.', '/') + ".class")) {
ClassNode ret = new ClassNode();
new ClassReader(IOHelper.read(in)).accept(ret, flags);
return ret;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static ClassNode forClass(String clazz, int flags, ClassMetadataReader r) {
try {
ClassNode ret = new ClassNode();
r.acceptVisitor(clazz, ret, flags);
return ret;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static List<AnnotationNode> annots(String clazz, String method, ClassMetadataReader r) {
if (clazz.startsWith("L")) clazz = Type.getType(clazz).getInternalName();
try {
List<AnnotationNode> ret = new ArrayList<>();
ClassNode n = forClass(clazz, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG, r);
if (n.visibleAnnotations != null) ret.addAll(n.visibleAnnotations);
if (n.invisibleAnnotations != null) ret.addAll(n.invisibleAnnotations);
for (MethodNode m : n.methods)
if (method.equals(m.name)) {
if (m.visibleAnnotations != null) ret.addAll(m.visibleAnnotations);
if (m.invisibleAnnotations != null) ret.addAll(m.invisibleAnnotations);
}
return ret;
} catch (Throwable e) {
return Collections.emptyList();
}
}
private static int doMethodEmulation(String desc) {
int result = 0;
Type returnType = Type.getReturnType(desc);
if (returnType.getSort() == Type.LONG || returnType.getSort() == Type.DOUBLE)
result++;
if (returnType.getSort() != Type.VOID)
result++;
return result;
}
public static int opcodeEmulation(AbstractInsnNode e) {
int stackSize = 0;
switch (e.getOpcode()) {
case NOP:
case LALOAD: // (index, arrayref) -> (long, long_top)
case DALOAD: // (index, arrayref) -> (double, double_top)
case SWAP: // (value1, value2) -> (value2, value1)
case INEG:
case LNEG:
case FNEG:
case DNEG:
case IINC:
case I2F:
case L2D:
case F2I:
case D2L:
case I2B:
case I2C:
case I2S:
case GOTO:
case RETURN:
case NEWARRAY:
case ANEWARRAY:
case ARRAYLENGTH:
case CHECKCAST:
case INSTANCEOF:
// Does nothing
break;
case ACONST_NULL:
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
case FCONST_0:
case FCONST_1:
case FCONST_2:
case BIPUSH:
case SIPUSH:
case ILOAD:
case FLOAD:
case ALOAD:
case DUP:
case DUP_X1:
case DUP_X2:
case I2L:
case I2D:
case F2L:
case F2D:
case NEW:
// Pushes one-word constant to stack
stackSize++;
break;
case LDC:
LdcInsnNode ldc = (LdcInsnNode) e;
if (ldc.cst instanceof Long || ldc.cst instanceof Double)
stackSize++;
stackSize++;
break;
case LCONST_0:
case LCONST_1:
case DCONST_0:
case DCONST_1:
case LLOAD:
case DLOAD:
case DUP2:
case DUP2_X1:
case DUP2_X2:
// Pushes two-word constant or two one-word constants to stack
stackSize++;
stackSize++;
break;
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKEINTERFACE:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKESTATIC:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKEDYNAMIC:
stackSize += doMethodEmulation(((InvokeDynamicInsnNode) e).desc);
break;
case JSR:
case RET:
throw new RuntimeException("Did not expect JSR/RET instructions");
default:
break;
}
return stackSize;
}
}

View file

@ -77,7 +77,7 @@ public static void apply(Path inputFile, Path addFile, ZipOutputStream output, L
try {
bytes = classFix(bytes, reader, srv.config.launcher.stripLineNumbers);
} catch (Throwable t) {
LogHelper.subWarning("Error on fixing class: " + t);
LogHelper.error(t);
}
output.write(bytes);
} else

View file

@ -3,7 +3,6 @@
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.dao.User;
import pro.gravit.launchserver.dao.UserHWID;
import pro.gravit.utils.helper.LogHelper;
public class GetUserCommand extends Command {
@ -31,9 +30,9 @@ public void invoke(String... args) throws Exception {
return;
}
LogHelper.info("[%s] UUID: %s", user.username, user.uuid.toString());
for(UserHWID hwid : user.hwids)
{
LogHelper.info("[%s] HWID: memory: %d | serial %s | hwdiskserial: %s | processorID %s | macAddr %s", user.username, hwid.totalMemory, hwid.serialNumber, hwid.HWDiskSerial, hwid.processorID, hwid.macAddr);
}
//for(UserHWID hwid : user.hwids)
//{
// LogHelper.info("[%s] HWID: memory: %d | serial %s | hwdiskserial: %s | processorID %s | macAddr %s", user.username, hwid.totalMemory, hwid.serialNumber, hwid.HWDiskSerial, hwid.processorID, hwid.macAddr);
//}
}
}

View file

@ -4,18 +4,13 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import pro.gravit.launcher.ClientPermissions;
@ -39,12 +34,6 @@ public class User {
public String serverID;
private String password_salt;
public long permissions;
//TODO: заменить EAGER на LASY и придумать способ сохранить сессию
// [ERROR] org.hibernate.LazyInitializationException:
// failed to lazily initialize a collection of role: pro.gravit.launchserver.dao.User.hwids, could not initialize proxy - no Session
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
public Collection<UserHWID> hwids;
public void setPassword(String password)
{
password_salt = SecurityHelper.randomStringAESKey();

View file

@ -0,0 +1,40 @@
{
"version": "1.14.4",
"assetIndex": "1.14.4",
"assetDir": "asset1.14.4",
"dir": "HiTech",
"info": "Информация о сервере",
"sortIndex": 0,
"title": "xxxxxxxx",
"serverAddress": "localhost",
"serverPort": 25565,
"update": [
"servers.dat"
],
"updateExclusions": [],
"updateShared": [],
"updateVerify": [
"libraries",
"natives",
"minecraft.jar"
],
"updateOptional": [],
"updateFastCheck": true,
"useWhitelist": false,
"mainClass": "net.minecraft.client.main.Main",
"jvmArgs": [
"-Dfml.ignorePatchDiscrepancies=true",
"-Dfml.ignoreInvalidMinecraftCertificates=rue",
"-XX:+UseConcMarkSweepGC",
"-XX:+CMSIncrementalMode",
"-XX:-UseAdaptiveSizePolicy",
"-Xmn128M",
"-XX:+DisableAttachMechanism"
],
"classPath": [
"minecraft.jar",
"libraries"
],
"clientArgs": [],
"whitelist": []
}

View file

@ -20,9 +20,9 @@
-keepattributes Signature
-adaptresourcefilecontents META-INF/MANIFEST.MF
-keeppackagenames com.mojang.**,net.minecraftforge.fml.**,cpw.mods.fml.**,com.google.gson.**,pro.gravit.repackage.**,org.fusesource.**
-keeppackagenames com.mojang.**,net.minecraftforge.fml.**,cpw.mods.fml.**,com.google.gson.**,pro.gravit.repackage.**,org.fusesource.**, pro.gravit.launcher.api.**
-keep class com.mojang.**,net.minecraftforge.fml.**,cpw.mods.fml.**,com.google.gson.**,pro.gravit.repackage.**,org.fusesource.** {
-keep class com.mojang.**,net.minecraftforge.fml.**,cpw.mods.fml.**,com.google.gson.**,pro.gravit.repackage.**,org.fusesource.**, pro.gravit.launcher.api.** {
*;
}

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -8,6 +9,7 @@
import java.util.List;
import pro.gravit.launcher.client.ClientLauncher;
import pro.gravit.launcher.client.ClientModuleManager;
import pro.gravit.launcher.client.DirBridge;
import pro.gravit.utils.helper.EnvHelper;
import pro.gravit.utils.helper.IOHelper;
@ -17,7 +19,9 @@
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 boolean noJava9check = Boolean.getBoolean(NO_JAVA9_CHECK_PROPERTY);
public static void main(String[] arguments) throws IOException, InterruptedException {
LogHelper.printVersion("Launcher");
@ -26,6 +30,9 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
JVMHelper.verifySystemProperties(Launcher.class, true);
EnvHelper.checkDangerousParams();
LauncherConfig config = Launcher.getConfig();
LauncherEngine.modulesManager = new ClientModuleManager();
LauncherConfig.getAutogenConfig().initModules();
LogHelper.info("Launcher for project %s", config.projectname);
if (config.environment.equals(LauncherConfig.LauncherEnvironment.PROD)) {
if (System.getProperty(LogHelper.DEBUG_PROPERTY) != null) {
@ -53,12 +60,34 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
JVMHelper.addSystemPropertyToArgs(args, DirBridge.CUSTOMDIR_PROPERTY);
JVMHelper.addSystemPropertyToArgs(args, DirBridge.USE_CUSTOMDIR_PROPERTY);
JVMHelper.addSystemPropertyToArgs(args, DirBridge.USE_OPTDIR_PROPERTY);
if (!noJava9check && !System.getProperty("java.version").startsWith("1.8"))
{
LogHelper.debug("Found Java 9+ ( %s )", System.getProperty("java.version"));
Collections.addAll(args, "--add-modules");
Collections.addAll(args, "javafx.base,javafx.fxml,javafx.controls,jdk.unsupported");
Path jvmDir = Paths.get(System.getProperty("java.home"));
String pathToFx = System.getenv("PATH_TO_FX");
Path fxPath = pathToFx == null ? null : Paths.get(pathToFx);
StringBuilder builder = new StringBuilder();
Path[] findPath = new Path[]{jvmDir, fxPath};
tryAddModule(findPath, "javafx.base", builder);
tryAddModule(findPath, "javafx.graphics", builder);
tryAddModule(findPath, "javafx.fxml", builder);
tryAddModule(findPath, "javafx.controls", builder);
String modulePath = builder.toString();
if(!modulePath.isEmpty())
{
Collections.addAll(args, "--module-path");
Collections.addAll(args, modulePath);
}
}
Collections.addAll(args, MAGIC_ARG);
Collections.addAll(args, "-XX:+DisableAttachMechanism");
Collections.addAll(args, "-javaagent:".concat(pathLauncher).concat("=pr"));
Collections.addAll(args, "-javaagent:".concat(pathLauncher));
Collections.addAll(args, "-cp");
Collections.addAll(args, pathLauncher);
Collections.addAll(args, LauncherEngine.class.getName());
LauncherEngine.modulesManager.callWrapper(processBuilder, args);
EnvHelper.addEnv(processBuilder);
LogHelper.debug("Commandline: " + args);
processBuilder.command(args);
@ -78,4 +107,30 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
process.waitFor();
}
}
public static Path tryFindModule(Path path, String moduleName)
{
Path result = path.resolve(moduleName.concat(".jar"));
LogHelper.dev("Try resolve %s", result.toString());
if(!IOHelper.isFile(result))
result = path.resolve("lib").resolve(moduleName.concat(".jar"));
else return result;
if(!IOHelper.isFile(result))
return null;
else return result;
}
public static boolean tryAddModule(Path[] paths, String moduleName, StringBuilder args)
{
for(Path path : paths)
{
if(path == null) continue;
Path result = tryFindModule(path, moduleName);
if(result != null)
{
if(args.length() != 0) args.append(File.pathSeparatorChar);
args.append(result.toAbsolutePath().toString());
return true;
}
}
return false;
}
}

View file

@ -1,29 +1,14 @@
package pro.gravit.launcher;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ARETURN;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.jar.JarFile;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodNode;
import cpw.mods.fml.SafeExitJVMLegacy;
import net.minecraftforge.fml.SafeExitJVM;
import pro.gravit.launcher.utils.NativeJVMHalt;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
@LauncherAPI
@ -52,138 +37,16 @@ public static void premain(String agentArgument, Instrumentation instrumentation
SafeExitJVM.class.getName();
NativeJVMHalt.class.getName();
NativeJVMHalt.initFunc();
isAgentStarted = true;
if (System.getProperty("java.vm.name").toUpperCase(Locale.US).contains("HOTSPOT"))
boolean bad = false;
try {
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
boolean pb = true;
boolean rt = true;
if (agentArgument != null) {
String trimmedArg = agentArgument.trim();
if (!trimmedArg.isEmpty()) {
if (trimmedArg.contains("p")) pb = false;
if (trimmedArg.contains("r")) rt = false;
}
}
replaceClasses(pb, rt);
} else replaceClasses(false, false);
} catch (Error e) {
NativeJVMHalt.haltA(294);
throw e;
}
for (StackTraceElement e : new Throwable().getStackTrace())
if (Class.forName(e.getClassName()).getClassLoader() != Runtime.class.getClassLoader() && Class.forName(e.getClassName()) != LauncherAgent.class) bad = true;
} catch(Throwable e) { bad = true; }
if (bad) NativeJVMHalt.haltA(-17);
else isAgentStarted = true;
}
public static boolean isStarted() {
return isAgentStarted;
}
private static void replaceClasses(boolean pb, boolean rt) {
java.awt.Robot.class.getName();
List<java.lang.instrument.ClassDefinition> defs = new ArrayList<>();
if (rt) {
try {
defs.add(new java.lang.instrument.ClassDefinition(java.lang.Runtime.class, transformClass(java.lang.Runtime.class.getName(), getClassFile(java.lang.Runtime.class))));
} catch (Exception e) {
throw new Error(e);
}
}
if (pb) {
try {
defs.add(new java.lang.instrument.ClassDefinition(java.lang.ProcessBuilder.class, transformClass(java.lang.ProcessBuilder.class.getName(), getClassFile(java.lang.ProcessBuilder.class))));
} catch (Exception e) {
throw new Error(e);
}
}
try {
defs.add(new java.lang.instrument.ClassDefinition(java.awt.Robot.class, transformClass(java.awt.Robot.class.getName(), getClassFile(java.awt.Robot.class))));
} catch (Exception e) {
throw new Error(e);
}
try {
inst.redefineClasses(defs.toArray(new java.lang.instrument.ClassDefinition[0]));
} catch (Exception e) {
throw new Error(e);
}
}
/**
* @author https://github.com/Konloch/JVM-Sandbox
* Use ASM to modify the byte array
*/
private static byte[] transformClass(String className, byte[] classBytes) {
switch (className) {
case "java.lang.Runtime": {
ClassReader cr = new ClassReader(classBytes);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
for (Object o : cn.methods.toArray()) {
MethodNode m = (MethodNode) o;
if (m.name.equals("exec")) {
m.instructions.insert(new InsnNode(ARETURN));
m.instructions.insert(new InsnNode(ACONST_NULL));
}
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
case "java.lang.ProcessBuilder": {
ClassReader cr = new ClassReader(classBytes);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
for (Object o : cn.methods.toArray()) {
MethodNode m = (MethodNode) o;
if (m.name.equals("start")) {
m.instructions.insert(new InsnNode(ARETURN));
m.instructions.insert(new InsnNode(ACONST_NULL));
}
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
case "java.awt.Robot": {
ClassReader cr = new ClassReader(classBytes);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
for (Object o : cn.methods.toArray()) {
MethodNode m = (MethodNode) o;
if (m.name.equals("createScreenCapture") || m.name.equals("getPixelColor") ||
m.name.equals("keyPress") || m.name.equals("keyRelease") ||
m.name.equals("mouseMove") || m.name.equals("mousePress") ||
m.name.equals("mouseWheel")) {
m.instructions.insert(new InsnNode(ARETURN));
m.instructions.insert(new InsnNode(ACONST_NULL));
}
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
}
return classBytes;
}
/**
* @param clazz
* @return array, respending this class in bytecode.
* @throws IOException
* @author https://github.com/Konloch/JVM-Sandbox
* Do not remove this method. Do not to cause classloading!
* Grab the byte array from the loaded Class object
*/
private static byte[] getClassFile(Class<?> clazz) throws IOException {
try (InputStream is = clazz.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class");
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int r;
byte[] buffer = new byte[8192];
while ((r = is.read(buffer)) >= 0) {
baos.write(buffer, 0, r);
}
return baos.toByteArray();
}
}
}

View file

@ -0,0 +1,19 @@
package pro.gravit.launcher.api;
import pro.gravit.launcher.ClientPermissions;
import java.util.UUID;
public class AuthService {
public static String username;
public static ClientPermissions permissions = new ClientPermissions();
public static UUID uuid;
public static boolean isAdmin()
{
return permissions.canAdmin;
}
public static boolean isServer()
{
return permissions.canServer;
}
}

View file

@ -0,0 +1,10 @@
package pro.gravit.launcher.api;
import java.lang.instrument.Instrumentation;
import java.net.URL;
public class ClientService {
public static Instrumentation instrumentation;
public static ClassLoader classLoader;
public static URL[] baseURLs;
}

View file

@ -32,7 +32,11 @@
import pro.gravit.launcher.LauncherAgent;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.api.AuthService;
import pro.gravit.launcher.api.ClientService;
import pro.gravit.launcher.client.events.ClientLaunchPhase;
import pro.gravit.launcher.client.events.ClientLauncherInitPhase;
import pro.gravit.launcher.client.events.ClientLauncherPostInitPhase;
import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.gui.JSRuntimeProvider;
import pro.gravit.launcher.hasher.FileNameMatcher;
@ -40,7 +44,6 @@
import pro.gravit.launcher.hwid.HWIDProvider;
import pro.gravit.launcher.managers.ClientGsonManager;
import pro.gravit.launcher.managers.ClientHookManager;
import pro.gravit.launcher.modules.events.PostInitPhase;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.profiles.PlayerProfile;
@ -51,6 +54,7 @@
import pro.gravit.launcher.serialize.HOutput;
import pro.gravit.launcher.serialize.stream.StreamObject;
import pro.gravit.launcher.utils.DirWatcher;
import pro.gravit.launcher.utils.NativeJVMHalt;
import pro.gravit.utils.PublicURLClassLoader;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.CommonHelper;
@ -300,11 +304,11 @@ private static void launch(ClientProfile profile, Params params) throws Throwabl
LogHelper.debug("Args: " + copy);
// Resolve main class and method
Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
MethodHandle mainMethod = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
MethodHandle mainMethod = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
Launcher.LAUNCHED.set(true);
JVMHelper.fullGC();
// Invoke main method
mainMethod.invoke((Object) args.toArray(new String[0]));
mainMethod.invokeWithArguments((Object)args.toArray(new String[0]));
}
private static Process process = null;
@ -410,9 +414,16 @@ public static Process launch(
builder.redirectErrorStream(true);
builder.redirectOutput(Redirect.PIPE);
}
List<String> command = builder.command();
// Let's rock!
ClientHookManager.preStartHook.hook(context, builder);
process = builder.start();
if (builder.command() != command) {
LogHelper.error("Something strange cheating...");
System.exit(100);
clientStarted = false;
return null;
}
if(ClientHookManager.postStartHook.hook(context, builder)) return process;
if (!pipeOutput) {
for (int i = 0; i < 50; ++i) {
@ -434,6 +445,21 @@ public static Process launch(
clientStarted = false;
return process;
}
public static class ClientLaunchContext
{
public final Params params;
public final ClientProfile profile;
public final HashedDir assetHDir, clientHDir;
public DirWatcher assetWatcher, clientWatcher;
public ClientLaunchContext(Params params, ClientProfile profile, HashedDir assetHDir, HashedDir clientHDir) {
this.params = params;
this.profile = profile;
this.assetHDir = assetHDir;
this.clientHDir = clientHDir;
}
}
@LauncherAPI
public static void main(String... args) throws Throwable {
@ -444,6 +470,10 @@ public static void main(String... args) throws Throwable {
LauncherEngine.modulesManager.initModules(null);
initGson(LauncherEngine.modulesManager);
//Launcher.modulesManager.preInitModules();
if (!LauncherAgent.isStarted()) {
NativeJVMHalt.haltA(100);
return;
}
LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase());
JVMHelper.verifySystemProperties(ClientLauncher.class, true);
EnvHelper.checkDangerousParams();
@ -474,11 +504,12 @@ public static void main(String... args) throws Throwable {
System.exit(-98);
return;
}
ClientLaunchContext context = new ClientLaunchContext(params, profile, assetHDir, clientHDir);
Launcher.profile = profile;
playerProfile = params.pp;
Request.setSession(params.session);
checkJVMBitsAndVersion();
LauncherEngine.modulesManager.invokeEvent(new ClientLauncherInitPhase());
LauncherEngine.modulesManager.invokeEvent(new ClientLauncherInitPhase(context));
// Verify ClientLauncher sign and classpath
LogHelper.debug("Verifying ClientLauncher sign and classpath");
LinkedList<Path> classPath = resolveClassPathList(params.clientDir, profile.getClassPath());
@ -518,6 +549,11 @@ public static void main(String... args) throws Throwable {
LogHelper.error(e);
}
};
AuthService.username = params.pp.username;
AuthService.uuid = params.pp.uuid;
ClientService.instrumentation = LauncherAgent.inst;
ClientService.classLoader = classLoader;
ClientService.baseURLs = classpathurls;
LogHelper.debug("Starting JVM and client WatchService");
FileNameMatcher assetMatcher = profile.getAssetUpdateMatcher();
FileNameMatcher clientMatcher = profile.getClientUpdateMatcher();
@ -529,13 +565,16 @@ public static void main(String... args) throws Throwable {
// if (params.updateOptional.contains(s)) s.mark = true;
// else hdir.removeR(s.file);
//}
context.assetWatcher = assetWatcher;
context.clientWatcher = clientWatcher;
Launcher.profile.pushOptionalFile(clientHDir, false);
LauncherEngine.modulesManager.invokeEvent(new PostInitPhase());
LauncherEngine.modulesManager.invokeEvent(new ClientLauncherPostInitPhase(context));
// Start WatchService, and only then client
CommonHelper.newThread("Asset Directory Watcher", true, assetWatcher).start();
CommonHelper.newThread("Client Directory Watcher", true, clientWatcher).start();
verifyHDir(params.assetDir, assetHDir, assetMatcher, digest);
verifyHDir(params.clientDir, clientHDir, clientMatcher, digest);
LauncherEngine.modulesManager.invokeEvent(new ClientLaunchPhase(context));
launch(profile, params);
}
}

View file

@ -2,6 +2,7 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
@ -25,4 +26,14 @@ public void autoload(Path dir) throws IOException {
public LauncherModule loadModule(Path file) throws IOException {
throw new UnsupportedOperationException();
}
public void callWrapper(ProcessBuilder processBuilder, Collection<String> jvmArgs)
{
for(LauncherModule module : modules)
{
if(module instanceof ClientWrapperModule)
{
((ClientWrapperModule) module).wrapperPhase(processBuilder, jvmArgs);
}
}
}
}

View file

@ -0,0 +1,7 @@
package pro.gravit.launcher.client;
import java.util.Collection;
public interface ClientWrapperModule {
void wrapperPhase(ProcessBuilder processBuilder, Collection<String> jvmArgs);
}

View file

@ -7,6 +7,7 @@
import java.util.concurrent.atomic.AtomicReference;
import pro.gravit.launcher.LauncherAPI;
import pro.gravit.launcher.api.AuthService;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.hasher.FileNameMatcher;
@ -100,6 +101,12 @@ public static void setAuthParams(AuthRequestEvent event) {
Request.setSession(event.session);
}
LauncherGuardManager.guard.setProtectToken(event.protectToken);
AuthService.permissions = event.permissions;
if(event.playerProfile != null)
{
AuthService.username = event.playerProfile.username;
AuthService.uuid = event.playerProfile.uuid;
}
}
@FunctionalInterface

View file

@ -76,6 +76,7 @@ public void postDiff(UpdateRequest request, UpdateRequestEvent e, HashedDir.Diff
LogHelper.debug("Copy file %s to %s", ret.toAbsolutePath().toString(), source.toAbsolutePath().toString());
}
//Let's go!
Files.deleteIfExists(source);
Files.copy(ret, source);
try (InputStream input = IOHelper.newInput(ret)) {
IOHelper.transfer(input, source);

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.client.events;
import pro.gravit.launcher.client.ClientLauncher;
import pro.gravit.launcher.modules.LauncherModule;
public class ClientLaunchPhase extends LauncherModule.Event {
public final ClientLauncher.ClientLaunchContext context;
public ClientLaunchPhase(ClientLauncher.ClientLaunchContext context) {
this.context = context;
}
}

View file

@ -1,6 +1,12 @@
package pro.gravit.launcher.client.events;
import pro.gravit.launcher.client.ClientLauncher;
import pro.gravit.launcher.modules.events.InitPhase;
public class ClientLauncherInitPhase extends InitPhase {
public final ClientLauncher.ClientLaunchContext context;
public ClientLauncherInitPhase(ClientLauncher.ClientLaunchContext context) {
this.context = context;
}
}

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.client.events;
import pro.gravit.launcher.client.ClientLauncher;
import pro.gravit.launcher.modules.events.PostInitPhase;
public class ClientLauncherPostInitPhase extends PostInitPhase {
public final ClientLauncher.ClientLaunchContext context;
public ClientLauncherPostInitPhase(ClientLauncher.ClientLaunchContext context) {
this.context = context;
}
}

View file

@ -23,19 +23,15 @@ public class LauncherGravitGuard implements LauncherGuardInterface {
@Override
public String getName() {
return "wrapper";
return "gravitguard";
}
@Override
public Path getJavaBinPath() {
if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) {
String projectName = Launcher.getConfig().projectname;
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
return DirBridge.getGuardDir().resolve(wrapperUnpackName);
} else if (ClientLauncher.getJavaBinPath() != null) {
javaBinPath = ClientLauncher.getJavaBinPath();
String projectName = Launcher.getConfig().projectname;
String wrapperUnpackName = JVMHelper.JVM_BITS == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
String wrapperUnpackName = ( javaBinPath == null ? JVMHelper.JVM_BITS : JVMHelper.OS_BITS ) == 64 ? projectName.concat("64.exe") : projectName.concat("32.exe");
return DirBridge.getGuardDir().resolve(wrapperUnpackName);
} else
return IOHelper.resolveJavaBin(Paths.get(System.getProperty("java.home")));
@ -43,22 +39,26 @@ public Path getJavaBinPath() {
@Override
public int getClientJVMBits() {
return JVMHelper.JVM_BITS;
//При использовании GravitGuard без своей джавы
//Если при запуске лаунчера используется 32 бит джава, а ОС 64бит
//То в окне настроек будет отображаться >1.б доступной памяти
//Однако при выставлении >1.б JVM x32 работать откажеться
return JVMHelper.OS_BITS;
}
@Override
public void init(boolean clientInstance) {
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));
UnpackHelper.unpack(Launcher.getResourceURL("wrapper32.exe", "guard"), DirBridge.getGuardDir().resolve(projectName.concat("64.exe")));
UnpackHelper.unpack(Launcher.getResourceURL("AntiInject64.dll", "guard"), DirBridge.getGuardDir().resolve("AntiInject64.dll"));
UnpackHelper.unpack(Launcher.getResourceURL("wrapper32.exe", "guard"), DirBridge.getGuardDir().resolve(projectName.concat("32.exe")));
UnpackHelper.unpack(Launcher.getResourceURL("AntiInject32.dll", "guard"), DirBridge.getGuardDir().resolve("AntiInject32.dll"));
} catch (IOException e) {
throw new SecurityException(e);
}
if (clientInstance) GravitGuardBridge.callGuard();
if (clientInstance && JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) GravitGuardBridge.callGuard();
}
@Override

View file

@ -1,7 +1,6 @@
package pro.gravit.launcher.managers;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@ -102,9 +101,4 @@ public void loadHDirStore() throws IOException {
public void saveHDirStore() throws IOException {
saveHDirStore(DirBridge.dirProjectStore);
}
@Override
public void setType(Type type) {
super.setType(type);
}
}

View file

@ -1,29 +1,13 @@
package pro.gravit.launcher.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Path;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherAPI;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
public abstract class JsonConfigurable<T> {
private Type type;
protected Path configPath;
@LauncherAPI
public void saveConfig() throws IOException {
saveConfig(configPath);
}
@LauncherAPI
public void loadConfig() throws IOException {
loadConfig(configPath);
}
public abstract class JsonConfigurable<T> implements JsonConfigurableInterface<T> {
private transient final Type type;
protected transient final Path configPath;
@LauncherAPI
public JsonConfigurable(Type type, Path configPath) {
@ -31,55 +15,14 @@ public JsonConfigurable(Type type, Path configPath) {
this.configPath = configPath;
}
@LauncherAPI
public void saveConfig(Path configPath) throws IOException {
try (BufferedWriter writer = IOHelper.newWriter(configPath)) {
Launcher.gsonManager.configGson.toJson(getConfig(), type, writer);
}
@Override
public Path getPath() {
return configPath;
}
@LauncherAPI
public void loadConfig(Path configPath) throws IOException {
if (generateConfigIfNotExists(configPath)) return;
try (BufferedReader reader = IOHelper.newReader(configPath)) {
setConfig(Launcher.gsonManager.configGson.fromJson(reader, type));
} catch (Exception e)
{
LogHelper.error(e);
resetConfig(configPath);
}
}
@LauncherAPI
public void resetConfig() throws IOException {
setConfig(getDefaultConfig());
saveConfig();
}
@LauncherAPI
public void resetConfig(Path newPath) throws IOException {
setConfig(getDefaultConfig());
saveConfig(newPath);
}
@LauncherAPI
public boolean generateConfigIfNotExists(Path path) throws IOException {
if (IOHelper.isFile(path))
return false;
resetConfig(path);
return true;
}
@LauncherAPI
public boolean generateConfigIfNotExists() throws IOException {
if (IOHelper.isFile(configPath))
return false;
resetConfig();
return true;
}
protected void setType(Type type) {
this.type = type;
@Override
public Type getType() {
return type;
}
@LauncherAPI

View file

@ -0,0 +1,84 @@
package pro.gravit.launcher.config;
import com.google.gson.Gson;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherAPI;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Path;
public interface JsonConfigurableInterface<T> {
@LauncherAPI
default void saveConfig() throws IOException {
saveConfig(getPath());
}
@LauncherAPI
default void loadConfig() throws IOException {
loadConfig(getPath());
}
@LauncherAPI
default void saveConfig(Gson gson, Path configPath) throws IOException {
try (BufferedWriter writer = IOHelper.newWriter(configPath)) {
gson.toJson(getConfig(), getType(), writer);
}
}
@LauncherAPI
default void loadConfig(Gson gson, Path configPath) throws IOException {
if (generateConfigIfNotExists(configPath)) return;
try (BufferedReader reader = IOHelper.newReader(configPath)) {
setConfig(gson.fromJson(reader, getType()));
} catch (Exception e)
{
LogHelper.error(e);
resetConfig(configPath);
}
}
@LauncherAPI
default void saveConfig(Path configPath) throws IOException {
saveConfig(Launcher.gsonManager.configGson, configPath);
}
@LauncherAPI
default void loadConfig(Path configPath) throws IOException {
loadConfig(Launcher.gsonManager.configGson, configPath);
}
@LauncherAPI
default void resetConfig() throws IOException {
setConfig(getDefaultConfig());
saveConfig();
}
@LauncherAPI
default void resetConfig(Path newPath) throws IOException {
setConfig(getDefaultConfig());
saveConfig(newPath);
}
@LauncherAPI
default boolean generateConfigIfNotExists(Path path) throws IOException {
if (IOHelper.isFile(path))
return false;
resetConfig(path);
return true;
}
@LauncherAPI
default boolean generateConfigIfNotExists() throws IOException {
if (IOHelper.isFile(getPath()))
return false;
resetConfig();
return true;
}
@LauncherAPI
T getConfig();
@LauncherAPI
T getDefaultConfig();
@LauncherAPI
void setConfig(T config);
@LauncherAPI
Path getPath();
Type getType();
}

View file

@ -0,0 +1,39 @@
package pro.gravit.launcher.config;
import java.lang.reflect.Type;
import java.nio.file.Path;
public abstract class SimpleConfig<T> implements JsonConfigurableInterface<T> {
private transient final Class<T> type;
protected transient final Path configPath;
protected SimpleConfig(Class<T> type, Path configPath) {
this.type = type;
this.configPath = configPath;
}
@SuppressWarnings("unchecked")
@Override
public T getConfig() {
return (T) this;
}
@Override
public T getDefaultConfig() {
try {
return type.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
return null;
}
}
@Override
public Path getPath() {
return configPath;
}
@Override
public Type getType() {
return type;
}
}

View file

@ -0,0 +1,32 @@
package pro.gravit.launcher.config;
import java.nio.file.Path;
public class SimpleConfigurable<T> extends JsonConfigurable<T> {
public T config;
private final Class<T> tClass;
public SimpleConfigurable(Class<T> type, Path configPath) {
super(type, configPath);
tClass = type;
}
@Override
public T getConfig() {
return config;
}
@Override
public T getDefaultConfig() {
try {
return tClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
return null;
}
}
@Override
public void setConfig(T config) {
this.config = config;
}
}

View file

@ -4,6 +4,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import pro.gravit.launcher.config.SimpleConfigurable;
import pro.gravit.launcher.modules.ModulesConfigManager;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
@ -34,4 +35,9 @@ public Path getModuleConfigDir(String moduleName) {
}
return configDir.resolve(moduleName);
}
@Override
public <T> SimpleConfigurable<T> getConfigurable(Class<T> tClass, Path configPath) {
return new SimpleConfigurable<>(tClass, configPath);
}
}

View file

@ -1,5 +1,7 @@
package pro.gravit.launcher.modules;
import pro.gravit.launcher.config.SimpleConfigurable;
import java.nio.file.Path;
public interface ModulesConfigManager {
@ -8,4 +10,16 @@ public interface ModulesConfigManager {
Path getModuleConfig(String moduleName, String configName);
Path getModuleConfigDir(String moduleName);
<T> SimpleConfigurable<T> getConfigurable(Class<T> tClass, Path configPath);
default <T> SimpleConfigurable<T> getConfigurable(Class<T> tClass, String moduleName)
{
return getConfigurable(tClass, getModuleConfig(moduleName));
}
default <T> SimpleConfigurable<T> getConfigurable(Class<T> tClass, String moduleName, String configName)
{
return getConfigurable(tClass, getModuleConfig(moduleName, configName));
}
}

View file

@ -1,6 +1,8 @@
package pro.gravit.launcher.request.update;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@ -26,6 +28,9 @@ public final class LauncherRequest extends Request<LauncherRequestEvent> impleme
@LauncherAPI
public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class);
@LauncherAPI
public static final Path C_BINARY_PATH = BINARY_PATH.getParent().resolve(IOHelper.getFileName(BINARY_PATH) + ".tmp");
@LauncherAPI
public static final boolean EXE_BINARY = IOHelper.hasExtension(BINARY_PATH, "exe");
@ -52,7 +57,12 @@ public static void update(LauncherRequestEvent result) throws IOException {
}*/
try {
ListDownloader downloader = new ListDownloader();
downloader.downloadOne(result.url, BINARY_PATH);
Files.deleteIfExists(C_BINARY_PATH);
downloader.downloadOne(result.url, C_BINARY_PATH);
try (InputStream in = IOHelper.newInput(C_BINARY_PATH)) {
IOHelper.transfer(in, BINARY_PATH);
}
Files.deleteIfExists(C_BINARY_PATH);
} catch (Throwable e) {
LogHelper.error(e);
}

View file

@ -4,7 +4,7 @@
import java.util.EnumSet;
import java.util.Set;
public final class MinecraftProfileTexture {
public class MinecraftProfileTexture {
public enum Type {
SKIN,
CAPE,

View file

@ -14,7 +14,7 @@
// Used to bypass Launcher's class name obfuscation and access API
@LauncherAPI
public final class CompatBridge {
public class CompatBridge {
public static final int PROFILES_MAX_BATCH_SIZE = SerializeLimits.MAX_BATCH_SIZE;
public static CompatProfile checkServer(String username, String serverID) throws Exception {

View file

@ -8,7 +8,7 @@
import pro.gravit.utils.helper.SecurityHelper;
@LauncherAPI
public final class CompatProfile {
public class CompatProfile {
public static final String SKIN_URL_PROPERTY = Launcher.SKIN_URL_PROPERTY;
public static final String SKIN_DIGEST_PROPERTY = Launcher.SKIN_DIGEST_PROPERTY;
public static final String CLOAK_URL_PROPERTY = Launcher.CLOAK_URL_PROPERTY;

View file

@ -9,7 +9,7 @@
// Used by 1.6.4 and below versions
@LauncherAPI
public final class LegacyBridge {
public class LegacyBridge {
public static boolean checkServer(String username, String serverID) throws Exception {
LogHelper.debug("LegacyBridge.checkServer, Username: '%s', Server ID: %s", username, serverID);
return new CheckServerRequest(username, serverID).request() != null;

View file

@ -10,7 +10,7 @@
import pro.gravit.utils.helper.LogHelper;
public final class YggdrasilAuthenticationService implements AuthenticationService {
public class YggdrasilAuthenticationService implements AuthenticationService {
public YggdrasilAuthenticationService(Proxy proxy, String clientToken) {
LogHelper.debug("Patched AuthenticationService created: '%s'", clientToken);
}

View file

@ -14,7 +14,7 @@
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.VerifyHelper;
public final class YggdrasilGameProfileRepository implements GameProfileRepository {
public class YggdrasilGameProfileRepository implements GameProfileRepository {
private static final long BUSY_WAIT_MS = VerifyHelper.verifyLong(
Long.parseLong(System.getProperty("launcher.com.mojang.authlib.busyWait", Long.toString(100L))),
VerifyHelper.L_NOT_NEGATIVE, "launcher.com.mojang.authlib.busyWait can't be < 0");

View file

@ -28,7 +28,7 @@
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
public final class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService {
public class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService {
public static final JsonParser JSON_PARSER = new JsonParser();
public static final boolean NO_TEXTURES = Boolean.parseBoolean("launcher.com.mojang.authlib.noTextures");

View file

@ -22,7 +22,7 @@ public final class Version {
public final Type release;
public static final int MAJOR = 5;
public static final int MINOR = 0;
public static final int PATCH = 8;
public static final int PATCH = 9;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.STABLE;

View file

@ -4,7 +4,7 @@
id 'signing'
}
group = 'pro.gravit.launcher'
version = '5.0.8'
version = '5.0.9'
configure(subprojects.findAll { it.name != 'modules' }) {
apply plugin: 'idea'

@ -1 +1 @@
Subproject commit 88bd03c38a2681e997a305e376eb63e446c99a7f
Subproject commit 8d7a95d0707539ab18fba83bef6ef9f09b70c0ad