2018-09-17 10:07:32 +03:00
|
|
|
package ru.gravit.launchserver.binary;
|
|
|
|
|
2018-09-17 10:20:34 +03:00
|
|
|
import static ru.gravit.utils.helper.IOHelper.newZipEntry;
|
2018-09-17 10:07:32 +03:00
|
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
2018-09-23 14:25:02 +03:00
|
|
|
import java.nio.file.*;
|
2018-09-17 10:07:32 +03:00
|
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
2018-11-26 10:43:18 +03:00
|
|
|
import java.util.StringTokenizer;
|
2018-09-17 10:07:32 +03:00
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.zip.ZipEntry;
|
|
|
|
import java.util.zip.ZipException;
|
|
|
|
import java.util.zip.ZipInputStream;
|
|
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
|
|
import javassist.CannotCompileException;
|
|
|
|
import javassist.NotFoundException;
|
|
|
|
import ru.gravit.launcher.AutogenConfig;
|
|
|
|
import ru.gravit.launcher.Launcher;
|
|
|
|
import ru.gravit.launcher.LauncherConfig;
|
2018-11-26 10:43:18 +03:00
|
|
|
import ru.gravit.utils.helper.CommonHelper;
|
|
|
|
import ru.gravit.utils.helper.EnvHelper;
|
2018-09-17 10:20:34 +03:00
|
|
|
import ru.gravit.utils.helper.IOHelper;
|
|
|
|
import ru.gravit.utils.helper.LogHelper;
|
|
|
|
import ru.gravit.utils.helper.SecurityHelper;
|
|
|
|
import ru.gravit.utils.helper.SecurityHelper.DigestAlgorithm;
|
2018-09-17 10:07:32 +03:00
|
|
|
import ru.gravit.launcher.serialize.HOutput;
|
|
|
|
import ru.gravit.launchserver.LaunchServer;
|
|
|
|
import proguard.Configuration;
|
|
|
|
import proguard.ConfigurationParser;
|
|
|
|
import proguard.ParseException;
|
|
|
|
import proguard.ProGuard;
|
|
|
|
|
|
|
|
public final class JARLauncherBinary extends LauncherBinary {
|
2018-10-13 07:59:50 +03:00
|
|
|
|
|
|
|
public static final String[] guardFileList = {"Avanguard64.dll", "Avanguard32.dll", "wrapper64.exe", "wrapper32.exe"};
|
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
private final class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
|
|
|
|
private final ZipOutputStream output;
|
|
|
|
private final Map<String, byte[]> runtime;
|
|
|
|
|
|
|
|
private RuntimeDirVisitor(ZipOutputStream output, Map<String, byte[]> runtime) {
|
|
|
|
this.output = output;
|
|
|
|
this.runtime = runtime;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
|
|
|
String dirName = IOHelper.toString(runtimeDir.relativize(dir));
|
|
|
|
output.putNextEntry(newEntry(dirName + '/'));
|
|
|
|
return super.preVisitDirectory(dir, attrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
|
|
String fileName = IOHelper.toString(runtimeDir.relativize(file));
|
|
|
|
runtime.put(fileName, SecurityHelper.digest(DigestAlgorithm.MD5, file));
|
|
|
|
|
|
|
|
// Create zip entry and transfer contents
|
|
|
|
output.putNextEntry(newEntry(fileName));
|
|
|
|
IOHelper.transfer(file, output);
|
|
|
|
|
|
|
|
// Return result
|
|
|
|
return super.visitFile(file, attrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-01 16:03:14 +03:00
|
|
|
private final class GuardDirVisitor extends SimpleFileVisitor<Path> {
|
|
|
|
private final ZipOutputStream output;
|
2018-11-01 16:45:11 +03:00
|
|
|
private final Map<String, byte[]> guard;
|
2018-11-01 16:03:14 +03:00
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
private GuardDirVisitor(ZipOutputStream output, Map<String, byte[]> guard) {
|
2018-11-01 16:03:14 +03:00
|
|
|
this.output = output;
|
2018-11-01 16:45:11 +03:00
|
|
|
this.guard = guard;
|
2018-11-01 16:03:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
|
|
|
String dirName = IOHelper.toString(guardDir.relativize(dir));
|
2018-11-01 16:45:11 +03:00
|
|
|
output.putNextEntry(newGuardEntry(dirName + '/'));
|
2018-11-01 16:03:14 +03:00
|
|
|
return super.preVisitDirectory(dir, attrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
|
|
String fileName = IOHelper.toString(guardDir.relativize(file));
|
2018-11-01 16:45:11 +03:00
|
|
|
guard.put(fileName, SecurityHelper.digest(DigestAlgorithm.MD5, file));
|
2018-11-01 16:03:14 +03:00
|
|
|
|
|
|
|
// Create zip entry and transfer contents
|
2018-11-01 16:45:11 +03:00
|
|
|
output.putNextEntry(newGuardEntry(fileName));
|
2018-11-01 16:03:14 +03:00
|
|
|
IOHelper.transfer(file, output);
|
|
|
|
|
|
|
|
// Return result
|
|
|
|
return super.visitFile(file, attrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
private static ZipEntry newEntry(String fileName) {
|
|
|
|
return newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName);
|
|
|
|
}
|
2018-11-08 15:30:16 +03:00
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
private static ZipEntry newGuardEntry(String fileName) {
|
|
|
|
return newZipEntry(Launcher.GUARD_DIR + IOHelper.CROSS_SEPARATOR + fileName);
|
|
|
|
}
|
2018-09-22 17:33:00 +03:00
|
|
|
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
public final Path runtimeDir;
|
2018-10-13 07:59:50 +03:00
|
|
|
public final Path guardDir;
|
2018-09-22 17:33:00 +03:00
|
|
|
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
public final Path initScriptFile;
|
|
|
|
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
public final Path obfJar;
|
|
|
|
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
public JARLauncherBinary(LaunchServer server) throws IOException {
|
2018-11-28 17:05:28 +03:00
|
|
|
super(server, server.dir.resolve(server.config.binaryName + ".jar"),
|
|
|
|
server.dir.resolve(server.config.binaryName + "-obf.jar"));
|
2018-09-22 17:33:00 +03:00
|
|
|
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
|
2018-10-13 07:59:50 +03:00
|
|
|
guardDir = server.dir.resolve("guard");
|
2018-09-22 17:33:00 +03:00
|
|
|
initScriptFile = runtimeDir.resolve(Launcher.INIT_SCRIPT_FILE);
|
2018-11-28 20:16:38 +03:00
|
|
|
obfJar = syncBinaryFile;
|
2018-09-22 17:33:00 +03:00
|
|
|
tryUnpackRuntime();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void build() throws IOException {
|
|
|
|
tryUnpackRuntime();
|
2018-10-13 07:59:50 +03:00
|
|
|
tryUnpackGuard();
|
2018-09-22 17:33:00 +03:00
|
|
|
|
|
|
|
// Build launcher binary
|
|
|
|
LogHelper.info("Building launcher binary file");
|
|
|
|
stdBuild();
|
|
|
|
|
|
|
|
// ProGuard
|
|
|
|
Configuration proguard_cfg = new Configuration();
|
|
|
|
ConfigurationParser parser = new ConfigurationParser(
|
|
|
|
server.proguardConf.confStrs.toArray(new String[0]),
|
|
|
|
server.proguardConf.proguard.toFile(), System.getProperties());
|
|
|
|
try {
|
|
|
|
parser.parse(proguard_cfg);
|
|
|
|
ProGuard proGuard = new ProGuard(proguard_cfg);
|
|
|
|
proGuard.execute();
|
|
|
|
} catch (ParseException e1) {
|
|
|
|
e1.printStackTrace();
|
|
|
|
}
|
2018-11-08 15:30:16 +03:00
|
|
|
if (server.buildHookManager.isNeedPostProguardHook()) {
|
2018-09-23 14:25:02 +03:00
|
|
|
Path obfPath = Paths.get(server.config.binaryName + "-obf.jar");
|
|
|
|
Path tmpPath = Paths.get(server.config.binaryName + "-tmp.jar");
|
2018-11-08 15:30:16 +03:00
|
|
|
IOHelper.move(obfPath, tmpPath);
|
|
|
|
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(obfPath))) {
|
2018-09-23 14:25:02 +03:00
|
|
|
try (ZipInputStream input = new ZipInputStream(
|
|
|
|
IOHelper.newInput(tmpPath))) {
|
|
|
|
ZipEntry e = input.getNextEntry();
|
|
|
|
while (e != null) {
|
|
|
|
String filename = e.getName();
|
|
|
|
output.putNextEntry(e);
|
|
|
|
if (filename.endsWith(".class")) {
|
|
|
|
CharSequence classname = filename.replace('/', '.').subSequence(0,
|
|
|
|
filename.length() - ".class".length());
|
|
|
|
byte[] bytes;
|
|
|
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048)) {
|
|
|
|
IOHelper.transfer(input, outputStream);
|
|
|
|
bytes = outputStream.toByteArray();
|
|
|
|
}
|
|
|
|
bytes = server.buildHookManager.proGuardClassTransform(bytes, classname);
|
|
|
|
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
|
|
|
|
IOHelper.transfer(inputStream, output);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
IOHelper.transfer(input, output);
|
|
|
|
e = input.getNextEntry();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-28 20:16:38 +03:00
|
|
|
//if (server.config.buildPostTransform.enabled)
|
|
|
|
// transformedBuild();
|
2018-09-22 17:33:00 +03:00
|
|
|
}
|
|
|
|
|
2018-11-26 10:43:18 +03:00
|
|
|
private void transformedBuild() throws IOException {
|
|
|
|
String cmd = CommonHelper.replace(server.config.buildPostTransform.script, "launcher-output", IOHelper.toAbsPathString(syncBinaryFile), "launcher-obf", IOHelper.toAbsPathString(obfJar), "launcher-nonObf", IOHelper.toAbsPathString(binaryFile));
|
|
|
|
ProcessBuilder builder = new ProcessBuilder();
|
|
|
|
builder.directory(IOHelper.toAbsPath(server.dir).toFile());
|
|
|
|
builder.inheritIO();
|
|
|
|
StringTokenizer st = new StringTokenizer(cmd);
|
|
|
|
String[] cmdarray = new String[st.countTokens()];
|
|
|
|
for (int i = 0; st.hasMoreTokens(); i++)
|
|
|
|
cmdarray[i] = st.nextToken();
|
|
|
|
builder.command(cmdarray);
|
|
|
|
Process proc = builder.start();
|
|
|
|
try {
|
|
|
|
LogHelper.debug("Transformer process return code: " + proc.waitFor());
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
LogHelper.error(e);
|
|
|
|
}
|
2018-09-22 17:33:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private void stdBuild() throws IOException {
|
|
|
|
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(binaryFile));
|
|
|
|
JAConfigurator jaConfigurator = new JAConfigurator(AutogenConfig.class)) {
|
|
|
|
BuildContext context = new BuildContext(output, jaConfigurator);
|
|
|
|
server.buildHookManager.preHook(context);
|
|
|
|
jaConfigurator.setAddress(server.config.getAddress());
|
|
|
|
jaConfigurator.setPort(server.config.port);
|
|
|
|
jaConfigurator.setProjectName(server.config.projectName);
|
2018-10-18 12:39:36 +03:00
|
|
|
jaConfigurator.setSecretKey(SecurityHelper.randomStringAESKey());
|
2018-10-13 11:20:23 +03:00
|
|
|
jaConfigurator.setClientPort(32148 + SecurityHelper.newRandom().nextInt(512));
|
2018-10-20 12:33:02 +03:00
|
|
|
jaConfigurator.setUsingWrapper(server.config.isUsingWrapper);
|
|
|
|
jaConfigurator.setDownloadJava(server.config.isDownloadJava);
|
2018-09-22 17:33:00 +03:00
|
|
|
server.buildHookManager.registerAllClientModuleClass(jaConfigurator);
|
|
|
|
try (ZipInputStream input = new ZipInputStream(
|
|
|
|
IOHelper.newInput(IOHelper.getResourceURL("Launcher.jar")))) {
|
|
|
|
ZipEntry e = input.getNextEntry();
|
|
|
|
while (e != null) {
|
|
|
|
String filename = e.getName();
|
|
|
|
if (server.buildHookManager.isContainsBlacklist(filename)) {
|
|
|
|
e = input.getNextEntry();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
output.putNextEntry(e);
|
|
|
|
} catch (ZipException ex) {
|
|
|
|
LogHelper.error(ex);
|
|
|
|
e = input.getNextEntry();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (filename.endsWith(".class")) {
|
|
|
|
CharSequence classname = filename.replace('/', '.').subSequence(0,
|
|
|
|
filename.length() - ".class".length());
|
|
|
|
byte[] bytes;
|
|
|
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048)) {
|
|
|
|
IOHelper.transfer(input, outputStream);
|
|
|
|
bytes = outputStream.toByteArray();
|
|
|
|
}
|
|
|
|
bytes = server.buildHookManager.classTransform(bytes, classname);
|
|
|
|
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
|
|
|
|
IOHelper.transfer(inputStream, output);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
IOHelper.transfer(input, output);
|
|
|
|
// }
|
|
|
|
e = input.getNextEntry();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// write additional classes
|
|
|
|
for (Entry<String, byte[]> ent : server.buildHookManager.getIncludeClass().entrySet()) {
|
|
|
|
output.putNextEntry(newZipEntry(ent.getKey().replace('.', '/').concat(".class")));
|
|
|
|
output.write(server.buildHookManager.classTransform(ent.getValue(), ent.getKey()));
|
|
|
|
}
|
2018-11-01 16:45:11 +03:00
|
|
|
// map for guard
|
2018-09-22 17:33:00 +03:00
|
|
|
Map<String, byte[]> runtime = new HashMap<>(256);
|
|
|
|
if (server.buildHookManager.buildRuntime()) {
|
|
|
|
// Verify has init script file
|
|
|
|
if (!IOHelper.isFile(initScriptFile))
|
|
|
|
throw new IOException(String.format("Missing init script file ('%s')", Launcher.INIT_SCRIPT_FILE));
|
2018-11-01 16:45:11 +03:00
|
|
|
// Write launcher guard dir
|
2018-09-22 17:33:00 +03:00
|
|
|
IOHelper.walk(runtimeDir, new RuntimeDirVisitor(output, runtime), false);
|
2018-11-27 14:19:09 +03:00
|
|
|
//IOHelper.walk(guardDir, new GuardDirVisitor(output, runtime), false);
|
2018-09-22 17:33:00 +03:00
|
|
|
}
|
|
|
|
// Create launcher config file
|
|
|
|
byte[] launcherConfigBytes;
|
|
|
|
try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) {
|
|
|
|
try (HOutput configOutput = new HOutput(configArray)) {
|
|
|
|
new LauncherConfig(server.config.getAddress(), server.config.port, server.publicKey, runtime)
|
|
|
|
.write(configOutput);
|
|
|
|
}
|
|
|
|
launcherConfigBytes = configArray.toByteArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write launcher config file
|
|
|
|
output.putNextEntry(newZipEntry(Launcher.CONFIG_FILE));
|
|
|
|
output.write(launcherConfigBytes);
|
|
|
|
ZipEntry e = newZipEntry(jaConfigurator.getZipEntryPath());
|
|
|
|
output.putNextEntry(e);
|
2018-10-07 12:06:07 +03:00
|
|
|
jaConfigurator.compile();
|
2018-09-22 17:33:00 +03:00
|
|
|
output.write(jaConfigurator.getBytecode());
|
|
|
|
server.buildHookManager.postHook(context);
|
|
|
|
} catch (CannotCompileException | NotFoundException e) {
|
|
|
|
LogHelper.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-09-22 17:33:00 +03:00
|
|
|
public void tryUnpackRuntime() throws IOException {
|
2018-11-01 16:45:11 +03:00
|
|
|
// Verify is guard dir unpacked
|
2018-09-22 17:33:00 +03:00
|
|
|
if (IOHelper.isDir(runtimeDir))
|
|
|
|
return; // Already unpacked
|
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
// Unpack launcher guard files
|
2018-09-22 17:33:00 +03:00
|
|
|
Files.createDirectory(runtimeDir);
|
|
|
|
LogHelper.info("Unpacking launcher runtime files");
|
|
|
|
try (ZipInputStream input = IOHelper.newZipInput(IOHelper.getResourceURL("runtime.zip"))) {
|
|
|
|
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
|
|
|
|
if (entry.isDirectory())
|
|
|
|
continue; // Skip dirs
|
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
// Unpack guard file
|
2018-09-22 17:33:00 +03:00
|
|
|
IOHelper.transfer(input, runtimeDir.resolve(IOHelper.toPath(entry.getName())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-13 11:01:10 +03:00
|
|
|
|
2018-10-13 07:59:50 +03:00
|
|
|
public void tryUnpackGuard() throws IOException {
|
2018-11-01 16:45:11 +03:00
|
|
|
// Verify is guard dir unpacked
|
2018-10-13 07:59:50 +03:00
|
|
|
if (IOHelper.isDir(guardDir))
|
|
|
|
return; // Already unpacked
|
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
// Unpack launcher guard files
|
2018-10-13 07:59:50 +03:00
|
|
|
Files.createDirectory(guardDir);
|
|
|
|
LogHelper.info("Unpacking launcher native guard files");
|
|
|
|
try (ZipInputStream input = IOHelper.newZipInput(IOHelper.getResourceURL("guard.zip"))) {
|
|
|
|
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
|
|
|
|
if (entry.isDirectory())
|
|
|
|
continue; // Skip dirs
|
|
|
|
|
2018-11-01 16:45:11 +03:00
|
|
|
// Unpack guard file
|
2018-10-13 07:59:50 +03:00
|
|
|
IOHelper.transfer(input, guardDir.resolve(IOHelper.toPath(entry.getName())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-17 10:07:32 +03:00
|
|
|
}
|