diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6a776e5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/store/
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..5cd5d90
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+ Builds, tests, and runs the project KnightInstaller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/commons-compress-1.21.jar b/lib/commons-compress-1.21.jar
new file mode 100644
index 0000000..4892334
Binary files /dev/null and b/lib/commons-compress-1.21.jar differ
diff --git a/lib/json-20220320.jar b/lib/json-20220320.jar
new file mode 100644
index 0000000..6121978
Binary files /dev/null and b/lib/json-20220320.jar differ
diff --git a/manifest.mf b/manifest.mf
new file mode 100644
index 0000000..328e8e5
--- /dev/null
+++ b/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644
index 0000000..870cb6c
--- /dev/null
+++ b/nbproject/build-impl.xml
@@ -0,0 +1,1799 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set platform.home
+ Must set platform.bootcp
+ Must set platform.java
+ Must set platform.javac
+
+ The J2SE Platform is not correctly set up.
+ Your active platform is: ${platform.active}, but the corresponding property "platforms.${platform.active}.home" is not found in the project's properties files.
+ Either open the project in the IDE and setup the Platform with the same name or add it manually.
+ For example like this:
+ ant -Duser.properties.file=<path_to_property_file> jar (where you put the property "platforms.${platform.active}.home" in a .properties file)
+ or ant -Dplatforms.${platform.active}.home=<path_to_JDK_home> jar (where no properties file is used)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No tests executed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set JVM to use for profiling in profiler.info.jvm
+ Must set profiler agent JVM arguments in profiler.info.jvmargs.agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ ${platform.java} -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set profile.class
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+ Must select some files in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+ Must select one file in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644
index 0000000..58e9de4
--- /dev/null
+++ b/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=af8d40f1
+build.xml.script.CRC32=2d8f044e
+build.xml.stylesheet.CRC32=f85dc8f2@1.102.0.48
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=af8d40f1
+nbproject/build-impl.xml.script.CRC32=7562ec59
+nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.102.0.48
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..d595bf7
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,101 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=KnightInstaller
+application.vendor=maks
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.modulepath=\
+ ${run.modulepath}
+debug.test.classpath=\
+ ${run.test.classpath}
+debug.test.modulepath=\
+ ${run.test.modulepath}
+# Files in build.classes.dir which should be excluded from distribution jar
+dist.archive.excludes=
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/KnightInstaller.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+dist.jlink.dir=${dist.dir}/jlink
+dist.jlink.output=${dist.jlink.dir}/KnightInstaller
+endorsed.classpath=
+excludes=
+file.reference.commons-compress-1.21.jar=lib/commons-compress-1.21.jar
+file.reference.json-20220320.jar=lib/json-20220320.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.commons-compress-1.21.jar}:\
+ ${file.reference.json-20220320.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.external.vm=true
+javac.modulepath=
+javac.processormodulepath=
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=1.8
+javac.target=1.8
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.modulepath=\
+ ${javac.modulepath}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.html5=false
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+# The jlink additional root modules to resolve
+jlink.additionalmodules=
+# The jlink additional command line parameters
+jlink.additionalparam=
+jlink.launcher=true
+jlink.launcher.name=KnightInstaller
+main.class=git.artdeell.knightinstaller.MainFrame
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=JDK_1.8
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.modulepath=\
+ ${javac.modulepath}
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+run.test.modulepath=\
+ ${javac.test.modulepath}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..c0c26b0
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,16 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ KnightInstaller
+
+
+
+
+
+
+
+
+
+
diff --git a/src/git/artdeell/knightinstaller/KnightInstaller.java b/src/git/artdeell/knightinstaller/KnightInstaller.java
new file mode 100644
index 0000000..a1eab6f
--- /dev/null
+++ b/src/git/artdeell/knightinstaller/KnightInstaller.java
@@ -0,0 +1,309 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Main.java to edit this template
+ */
+package git.artdeell.knightinstaller;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.GZIPInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.json.JSONObject;
+
+/**
+ *
+ * @author maks
+ */
+public class KnightInstaller implements Runnable {
+
+ private static final String HEAD_N = "`head -n ";
+ private static final String CODE_LINE = "code = ";
+ private static final String ARG_LINE = "jvmarg = ";
+ private static final String CLASS_LINE = "class = ";
+ private final Object installLock = new Object();
+ private final Progress pr;
+ private final File destination;
+ private final File spiral;
+ private final File getdown;
+
+ public KnightInstaller(Progress pr) {
+ this.pr = pr;
+ this.destination = new File(System.getProperty("user.home") + "/.minecraft");
+ this.spiral = new File(destination, "spiral");
+ this.getdown = new File(spiral, "getdown-pro.jar");
+ }
+
+ @Override
+ public void run() {
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public void checkPermission(Permission permission) {
+ if ("exitVM.0".equals(permission.getName())) {
+ synchronized (installLock) {
+ installLock.notifyAll();
+ }
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void checkExec(String cmd) {
+ synchronized (installLock) {
+ installLock.notifyAll();
+ }
+ throw new IllegalStateException();
+ }
+ });
+ int progressStep = 0;
+ if (!getdown.exists()) {
+ pr.postMaxSteps(5);
+ byte[] installer;
+ try {
+ pr.postLogLine("Downloading Linux installer...", null);
+ installer = Utils.getFromWeb("https://gamemedia.spiralknights.com/spiral/client/spiral-install.bin", pr);
+ pr.postStepProgress(++progressStep);
+ } catch (IOException e) {
+ pr.postLogLine("Failed to download Linux installer", e);
+ pr.unlockExit();
+ return;
+ }
+
+ int gzStart;
+ int gzSize;
+ try {
+ pr.postLogLine("Processing installer...", null);
+ pr.postMaxPart(1);
+ pr.postPartProgress(0);
+ pr.setPartIndeterminate(true);
+ byte[] offsetEquals = "offset=`head".getBytes();
+ byte[] filesizesEquals = "filesizes=".getBytes();
+ int offsetAt = Utils.indexOf(installer, offsetEquals);
+ int sizeAt = Utils.indexOf(installer, filesizesEquals);
+ if (offsetAt == -1 || sizeAt == -1) {
+ pr.postLogLine("Failed to find necessary data", null);
+ pr.setPartIndeterminate(false);
+ return;
+ }
+ int offsetSz = Utils.indexOf(installer, offsetAt, (byte) 0x0A);
+ int sizeSz = Utils.indexOf(installer, sizeAt, (byte) 0x0A);
+ offsetSz = offsetSz - offsetAt;
+ sizeSz = sizeSz - sizeAt;
+ String offset = new String(installer, offsetAt, offsetSz);
+ String size = new String(installer, sizeAt, sizeSz);
+ System.out.println(offset);
+ System.out.println(size);
+
+ int headNumStart = offset.indexOf(HEAD_N) + HEAD_N.length();
+ int headNumEnd = -1;
+ for (int i = headNumStart; i < offset.length(); i++) {
+ if (offset.charAt(i) == ' ') {
+ headNumEnd = i;
+ break;
+ }
+ }
+
+ if (headNumEnd == -1) {
+ pr.postLogLine("Failed to find necessary data", null);
+ pr.setPartIndeterminate(false);
+ return;
+ }
+ gzStart = Integer.parseInt(offset.substring(headNumStart, headNumEnd));
+ gzSize = Integer.parseInt(size.substring(size.indexOf("\"") + 1, size.lastIndexOf(("\""))));
+ gzStart = Utils.findXth(installer, (byte) 0x0A, gzStart) + 1;
+ pr.postStepProgress(++progressStep);
+ } catch (Exception e) {
+ pr.postLogLine("Failed to read necessary data", e);
+ pr.setPartIndeterminate(false);
+ pr.unlockExit();
+ return;
+ }
+ try {
+ pr.postLogLine("Decompressing...", null);
+ pr.postMaxPart(1);
+ pr.postPartProgress(0);
+ pr.setPartIndeterminate(true);
+ try ( TarArchiveInputStream tarIn = new TarArchiveInputStream(new GZIPInputStream(new ByteArrayInputStream(installer, gzStart, gzSize)))) {
+ TarArchiveEntry entry = tarIn.getNextTarEntry();
+ while (entry != null) {
+ String entryName = entry.getName();
+ pr.postLogLine("Decompressing " + entryName, null);
+ File dest = new File(spiral, entry.getName());
+ if (entry.isDirectory()) {
+ dest.mkdirs();
+ } else if (entry.isFile()) {
+ dest.getParentFile().mkdirs();
+ try ( FileOutputStream fos = new FileOutputStream(dest)) {
+ byte[] buf = new byte[65535];
+ int i;
+ while ((i = tarIn.read(buf)) != -1) {
+ fos.write(buf, 0, i);
+ }
+ }
+ }
+ entry = tarIn.getNextTarEntry();
+ }
+ }
+ pr.postStepProgress(++progressStep);
+ } catch (Exception e) {
+ pr.postLogLine("Failed to decompress", e);
+ pr.setPartIndeterminate(false);
+ pr.unlockExit();
+ return;
+ }
+ } else {
+ pr.postMaxSteps(2);
+ }
+ try {
+ pr.postLogLine("Starting getdown...", null);
+ pr.postMaxPart(1);
+ pr.postPartProgress(0);
+ pr.setPartIndeterminate(true);
+ if (!getdown.exists()) {
+ pr.postLogLine("Can't find getdown-pro.jar", null);
+ pr.setPartIndeterminate(false);
+ return;
+ }
+ JarFile zf = new JarFile(getdown);
+ Manifest mf = zf.getManifest();
+ if (mf == null) {
+ pr.postLogLine("Can't find Manifest in getdown-pro.jar", null);
+ pr.setPartIndeterminate(false);
+ return;
+ }
+ String mainClass = mf.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
+ if (mainClass == null) {
+ pr.postLogLine("No main class in getdown-pro.jar", null);
+ pr.setPartIndeterminate(false);
+ return;
+ }
+ URLClassLoader cl = new URLClassLoader(new URL[]{getdown.toURI().toURL()});
+ Class main = cl.loadClass(mainClass);
+ Method mainMethod = main.getDeclaredMethod("main", new Class[]{String[].class});
+ mainMethod.invoke(null, (Object) new String[]{spiral.getAbsolutePath()});
+ pr.postLogLine("Locking until the install is finished...", null);
+ synchronized (installLock) {
+ installLock.wait();
+ }
+ pr.moveToTop();
+ System.setSecurityManager(null);
+ pr.postStepProgress(++progressStep);
+ } catch (Exception e) {
+ pr.postLogLine("Failed to start getdown", e);
+ pr.setPartIndeterminate(false);
+ pr.unlockExit();
+ return;
+ }
+ try {
+ pr.postLogLine("Generating JSON...", null);
+ List codeJars = new ArrayList<>();
+ List jvmArgs = new ArrayList<>();
+ String mainClass = null;
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(new FileInputStream(new File(spiral, "getdown.txt"))));
+ String line;
+ while ((line = rdr.readLine()) != null) {
+ if (line.startsWith(CODE_LINE)) {
+ String codeJar = line.substring(CODE_LINE.length());
+ if (!codeJar.contains("jinput") && !codeJar.contains("lwjgl")) {
+ codeJars.add(codeJar);
+ }
+ } else if (line.startsWith(ARG_LINE)) {
+ String arg = line.substring(ARG_LINE.length());
+ if (!arg.startsWith("-Xm") && !arg.startsWith("-Djava.library.path") && !arg.startsWith("[")) {
+ jvmArgs.add(arg);
+ }
+ } else if (line.startsWith(CLASS_LINE)) {
+ mainClass = line.substring(CLASS_LINE.length());
+ }
+ }
+ jvmArgs.add("-Dorg.lwjgl.opengl.disableStaticInit=true");
+ JSONObject outputJson = new JSONObject();
+ outputJson.put("minecraftArguments", "");
+ for (String s : codeJars) {
+ File source = new File(spiral, s);
+ String fileName = source.getName();
+ String extension = fileName.substring(fileName.lastIndexOf("."));
+ fileName = fileName.substring(0, fileName.lastIndexOf("."));
+ String libName = "spiral:" + fileName + ":0.0";
+ File libDestination = new File(destination, "libraries/spiral/" + fileName + "/0.0/" + fileName + "-0.0" + extension);
+ libDestination.getParentFile().mkdirs();
+ Files.copy(source.toPath(), libDestination.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ JSONObject library = new JSONObject();
+ library.put("name", libName);
+ outputJson.append("libraries", library);
+ }
+ outputJson.put("id", "SpiralKnights");
+ outputJson.put("releaseTime", "2009-05-13T20:11:00+00:00");
+ outputJson.put("time", "2009-05-13T20:11:00+00:00");
+ outputJson.put("type", "release");
+ outputJson.put("mainClass", mainClass);
+ File versionPath = new File(destination, "versions/SpiralKnights/SpiralKnights.json");
+ versionPath.getParentFile().mkdirs();
+ try ( FileOutputStream fos = new FileOutputStream(versionPath)) {
+ fos.write(outputJson.toString().getBytes());
+ }
+
+ String sprofiles = null;
+ String b64Default = null;
+ try {
+ byte[] bprofiles = Files.readAllBytes(new File(destination, "launcher_profiles.json").toPath());
+ sprofiles = new String(bprofiles, 0, bprofiles.length);
+ } catch (Exception ignored) {
+ }
+ try {
+ b64Default = Base64.getEncoder().encodeToString(Files.readAllBytes(new File(spiral, "desktop.png").toPath()));
+ } catch (Exception ignored) {
+ }
+
+ JSONObject profiles = sprofiles == null ? new JSONObject() : new JSONObject(sprofiles);
+ JSONObject spiralKnightsProfile = new JSONObject();
+ StringBuilder sb = new StringBuilder();
+ int sz = jvmArgs.size();
+ for (int i = 0; i < sz; i++) {
+ sb.append(jvmArgs.get(i).replace("%APPDIR%", "./spiral/"));
+ if (i < sz - 1) {
+ sb.append(" ");
+ }
+ }
+ spiralKnightsProfile.put("javaArgs", sb.toString());
+ spiralKnightsProfile.put("lastVersionId", "SpiralKnights");
+ spiralKnightsProfile.put("name", "Spiral Knights");
+ if (b64Default != null) {
+ spiralKnightsProfile.put("icon", "data:image/png;base64," + b64Default);
+ }
+ if (profiles.has("profiles")) {
+ profiles.getJSONObject("profiles").put("SpiralKnights", spiralKnightsProfile);
+ } else {
+ JSONObject newProfiles = new JSONObject();
+ newProfiles.put("SpiralKnights", spiralKnightsProfile);
+ profiles.put("profiles", newProfiles);
+ }
+
+ Files.write(new File(destination, "launcher_profiles.json").toPath(), profiles.toString().getBytes());
+ pr.postStepProgress(++progressStep);
+ pr.postLogLine("All done!", null);
+ pr.setPartIndeterminate(false);
+ } catch (Exception e) {
+ pr.postLogLine("Failed to generate JSON", e);
+ pr.setPartIndeterminate(false);
+ }
+ pr.unlockExit();
+ }
+}
diff --git a/src/git/artdeell/knightinstaller/MainFrame.form b/src/git/artdeell/knightinstaller/MainFrame.form
new file mode 100644
index 0000000..b93270f
--- /dev/null
+++ b/src/git/artdeell/knightinstaller/MainFrame.form
@@ -0,0 +1,88 @@
+
+
+
diff --git a/src/git/artdeell/knightinstaller/MainFrame.java b/src/git/artdeell/knightinstaller/MainFrame.java
new file mode 100644
index 0000000..9ab050e
--- /dev/null
+++ b/src/git/artdeell/knightinstaller/MainFrame.java
@@ -0,0 +1,189 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template
+ */
+package git.artdeell.knightinstaller;
+
+import java.awt.EventQueue;
+import java.awt.Frame;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author maks
+ */
+public class MainFrame extends javax.swing.JFrame implements Progress {
+ boolean buttonMode = true;
+ /**
+ * Creates new form MainFrame
+ */
+ public MainFrame() {
+ initComponents();
+ setLocationRelativeTo(null);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ stepProgressBar = new javax.swing.JProgressBar();
+ partProgressBar = new javax.swing.JProgressBar();
+ startButton = new javax.swing.JButton();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ logArea = new javax.swing.JTextArea();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ setAlwaysOnTop(true);
+
+ startButton.setText("Start");
+ startButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ startButtonActionPerformed(evt);
+ }
+ });
+
+ logArea.setColumns(20);
+ logArea.setRows(5);
+ logArea.setText("Press \"Start\" to install/update Spiral Knights\n\n");
+ jScrollPane1.setViewportView(logArea);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(jScrollPane1)
+ .addComponent(partProgressBar, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 388, Short.MAX_VALUE)
+ .addComponent(stepProgressBar, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
+ .addComponent(startButton)
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 216, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(stepProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(partProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(startButton)
+ .addContainerGap())
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void startButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startButtonActionPerformed
+ // TODO add your handling code here:
+ if(buttonMode) {
+ new Thread(new KnightInstaller(this)).start();
+ startButton.setEnabled(false);
+ }
+ else System.exit(0);
+ }//GEN-LAST:event_startButtonActionPerformed
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+
+ try {
+ javax.swing.UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
+ java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+ }
+ /* Create and display the form */
+ java.awt.EventQueue.invokeLater(() -> {
+ MainFrame mf = new MainFrame();
+ mf.setVisible(true);
+ });
+ }
+
+ @Override
+ public void postStepProgress(int prg) {
+ EventQueue.invokeLater(() -> stepProgressBar.setValue(prg));
+ }
+
+ @Override
+ public void postPartProgress(int prg) {
+ EventQueue.invokeLater(() -> partProgressBar.setValue(prg));
+ }
+
+ @Override
+ public void postMaxSteps(int max) {
+ EventQueue.invokeLater(() -> stepProgressBar.setMaximum(max));
+ }
+
+ @Override
+ public void postMaxPart(int max) {
+ EventQueue.invokeLater(() -> partProgressBar.setMaximum(max));
+ }
+
+ @Override
+ public void postLogLine(String line, Throwable th) {
+ EventQueue.invokeLater(() -> {
+ logArea.setText(logArea.getText() + line + "\n");
+ if (th != null) {
+ try (StringWriter swr = new StringWriter()) {
+ try ( PrintWriter wr = new PrintWriter(swr)) {
+ th.printStackTrace(wr);
+ logArea.setText(logArea.getText() + swr.toString());
+ }
+ } catch (IOException ex) {
+ Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ });
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JTextArea logArea;
+ private javax.swing.JProgressBar partProgressBar;
+ private javax.swing.JButton startButton;
+ private javax.swing.JProgressBar stepProgressBar;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void setPartIndeterminate(boolean indeterminate) {
+ EventQueue.invokeLater(() -> partProgressBar.setIndeterminate(indeterminate));
+ }
+
+ @Override
+ public void moveToTop() {
+ EventQueue.invokeLater(() -> {
+ //toFront();
+ hideAllForeignWindows();
+ });
+ }
+
+ @Override
+ public void unlockExit() {
+ EventQueue.invokeLater(() -> {
+ startButton.setText("Exit");
+ startButton.setEnabled(true);
+ buttonMode = false;
+ });
+ }
+ public void hideAllForeignWindows() {
+ for(Frame f : Frame.getFrames()) {
+ f.setVisible(false);
+ }
+ this.setVisible(true);
+ }
+}
diff --git a/src/git/artdeell/knightinstaller/Progress.java b/src/git/artdeell/knightinstaller/Progress.java
new file mode 100644
index 0000000..f01323f
--- /dev/null
+++ b/src/git/artdeell/knightinstaller/Progress.java
@@ -0,0 +1,20 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
+ */
+package git.artdeell.knightinstaller;
+
+/**
+ *
+ * @author maks
+ */
+public interface Progress {
+ void postStepProgress(int prg);
+ void postPartProgress(int prg);
+ void postMaxSteps(int max);
+ void postMaxPart(int max);
+ void setPartIndeterminate(boolean indeterminate);
+ void postLogLine(String line, Throwable th);
+ void moveToTop();
+ public void unlockExit();
+}
diff --git a/src/git/artdeell/knightinstaller/Utils.java b/src/git/artdeell/knightinstaller/Utils.java
new file mode 100644
index 0000000..70ba1e7
--- /dev/null
+++ b/src/git/artdeell/knightinstaller/Utils.java
@@ -0,0 +1,97 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package git.artdeell.knightinstaller;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ *
+ * @author maks
+ */
+public class Utils {
+ public static byte[] getFromWeb(String url, Progress pr) throws IOException{
+ boolean sendProgress = true;
+ HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
+ conn.connect();
+ if(conn.getContentLength() != -1) {
+ pr.postMaxPart(conn.getContentLength());
+ }else{
+ pr.setPartIndeterminate(true);
+ sendProgress = false;
+ }
+ InputStream is = conn.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];int i;int cur=0;
+ while((i = is.read(buf)) != -1) {
+ cur += i;
+ if(sendProgress) pr.postStepProgress(cur);
+ baos.write(buf, 0, i);
+ }
+ return baos.toByteArray();
+ }
+ public static int indexOf(byte[] haystack, byte[] needle)
+ {
+ // needle is null or empty
+ if (needle == null || needle.length == 0)
+ return 0;
+
+ // haystack is null, or haystack's length is less than that of needle
+ if (haystack == null || needle.length > haystack.length)
+ return -1;
+
+ // pre construct failure array for needle pattern
+ int[] failure = new int[needle.length];
+ int n = needle.length;
+ failure[0] = -1;
+ for (int j = 1; j < n; j++)
+ {
+ int i = failure[j - 1];
+ while ((needle[j] != needle[i + 1]) && i >= 0)
+ i = failure[i];
+ if (needle[j] == needle[i + 1])
+ failure[j] = i + 1;
+ else
+ failure[j] = -1;
+ }
+
+ // find match
+ int i = 0, j = 0;
+ int haystackLen = haystack.length;
+ int needleLen = needle.length;
+ while (i < haystackLen && j < needleLen)
+ {
+ if (haystack[i] == needle[j])
+ {
+ i++;
+ j++;
+ }
+ else if (j == 0)
+ i++;
+ else
+ j = failure[j - 1] + 1;
+ }
+ return ((j == needleLen) ? (i - needleLen) : -1);
+ }
+ public static int indexOf(byte[] array, int off, byte sym) {
+ for(int i = off; i < array.length; i++) {
+ if(array[i] == sym) return i;
+ }
+ return -1;
+ }
+ public static int findXth(byte[] array, byte sym, int cnt) {
+ int xthCount = 0;
+ for(int i = 0; i < array.length; i++) {
+ if(array[i] == sym) {
+ xthCount++;
+ if(xthCount == cnt) return i;
+ }
+ }
+ return -1;
+ }
+}