Compare commits

..

10 Commits

Author SHA1 Message Date
comp500
b3370739a5 Fix Swing multithreading issue, clean up slightly 2020-09-29 02:14:56 +01:00
comp500
ecc6f0440a Remove IntelliJ metadata from repo 2020-09-29 02:14:05 +01:00
comp500
92b44352b3 Fix RequestHandlerGithub heuristics, so that Github Releases files work properly 2020-06-20 03:15:18 +01:00
comp500
1d5a787b02 Add JvmStatic to fix --help command (bootstrapper calls these) 2020-06-16 04:05:02 +01:00
comp500
b5983800e8 Update README.md 2020-05-11 17:48:41 +01:00
comp500
4b3c279e71 Add support for loading from file:// URIs 2020-05-08 22:57:03 +01:00
comp500
b413371306 Fix --help command 2020-05-08 18:08:53 +01:00
comp500
1d2ec61232 Fix disgusting getNewLoc call (!! already checks null!!) 2020-02-07 03:12:52 +00:00
comp500
a0da889a02 Optimise memory usage while computing Murmur2 2019-12-23 16:31:36 +00:00
comp500
432bb4e25f Fix Murmur2 hash implementation 2019-12-23 16:20:38 +00:00
15 changed files with 161 additions and 178 deletions

30
.gitignore vendored
View File

@@ -1,9 +1,9 @@
# Created by https://www.gitignore.io/api/java,gradle,intellij # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all
# Edit at https://www.gitignore.io/?templates=java,gradle,intellij # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij+all
### Intellij ### ### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
@@ -33,6 +33,9 @@
# When using Gradle or Maven with auto-import, you should exclude module files, # When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
@@ -72,13 +75,18 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### Intellij Patch ### ### Intellij+all Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
# *.iml .idea/
# modules.xml
# .idea/misc.xml # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
# *.ipr
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin # Sonarlint plugin
.idea/sonarlint .idea/sonarlint
@@ -127,4 +135,4 @@ gradle-app.setting
### Gradle Patch ### ### Gradle Patch ###
**/build/ **/build/
# End of https://www.gitignore.io/api/java,gradle,intellij # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -1,36 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="wbp.parser.entryPoint" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component>
</project>

10
.idea/misc.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="com.google.gson.annotations.SerializedName" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,2 +1,2 @@
# packwiz-installer # packwiz-installer
An installer for launching packwiz modpacks with MultiMC. An installer for launching packwiz modpacks with MultiMC. You'll need [the bootstrapper](https://github.com/comp500/packwiz-installer-bootstrap/releases) to actually use this.

View File

@@ -74,13 +74,13 @@ public class Murmur2Lib {
int left = length - len_m; int left = length - len_m;
if (left != 0) { if (left != 0) {
if (left >= 3) { if (left >= 3) {
h ^= (int) data[length - 3] << 16; h ^= (int) data[length - (left - 2)] << 16;
} }
if (left >= 2) { if (left >= 2) {
h ^= (int) data[length - 2] << 8; h ^= (int) data[length - (left - 1)] << 8;
} }
if (left >= 1) { if (left >= 1) {
h ^= (int) data[length - 1]; h ^= data[length - left];
} }
h *= M_32; h *= M_32;
@@ -152,7 +152,7 @@ public class Murmur2Lib {
case 2: case 2:
h ^= (long) (data[tailStart + 1] & 0xff) << 8; h ^= (long) (data[tailStart + 1] & 0xff) << 8;
case 1: case 1:
h ^= (long) (data[tailStart] & 0xff); h ^= data[tailStart] & 0xff;
h *= M_64; h *= M_64;
} }

View File

@@ -1,3 +1,5 @@
@file:JvmName("Main")
package link.infra.packwiz.installer package link.infra.packwiz.installer
import link.infra.packwiz.installer.metadata.SpaceSafeURI import link.infra.packwiz.installer.metadata.SpaceSafeURI
@@ -16,6 +18,9 @@ import kotlin.system.exitProcess
@Suppress("unused") @Suppress("unused")
class Main(args: Array<String>) { class Main(args: Array<String>) {
// Don't attempt to start a GUI if we are headless
var guiEnabled = !GraphicsEnvironment.isHeadless()
private fun startup(args: Array<String>) { private fun startup(args: Array<String>) {
val options = Options() val options = Options()
addNonBootstrapOptions(options) addNonBootstrapOptions(options)
@@ -26,19 +31,24 @@ class Main(args: Array<String>) {
parser.parse(options, args) parser.parse(options, args)
} catch (e: ParseException) { } catch (e: ParseException) {
e.printStackTrace() e.printStackTrace()
try { if (guiEnabled) {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) EventQueue.invokeAndWait {
} catch (e1: Exception) { try {
// Ignore the exceptions, just continue using the ugly L&F UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
} catch (ignored: Exception) {
// Ignore the exceptions, just continue using the ugly L&F
}
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
}
} }
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
exitProcess(1) exitProcess(1)
} }
// if "headless", GUI creation will fail anyway! if (guiEnabled && cmd.hasOption("no-gui")) {
val ui = if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) { guiEnabled = false
CLIHandler() }
} else InstallWindow()
val ui = if (guiEnabled) InstallWindow() else CLIHandler()
val unparsedArgs = cmd.args val unparsedArgs = cmd.args
if (unparsedArgs.size > 1) { if (unparsedArgs.size > 1) {
@@ -47,15 +57,13 @@ class Main(args: Array<String>) {
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!")) ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!"))
} }
cmd.getOptionValue("title")?.also { cmd.getOptionValue("title")?.also(ui::setTitle)
ui.setTitle(it)
}
val inputStateHandler = InputStateHandler() val inputStateHandler = InputStateHandler()
ui.show(inputStateHandler) ui.show(inputStateHandler)
val uOptions = UpdateManager.Options().apply { val uOptions = UpdateManager.Options().apply {
side = cmd.getOptionValue("side")?.let { UpdateManager.Options.Side.from(it) } ?: side side = cmd.getOptionValue("side")?.let((UpdateManager.Options.Side)::from) ?: side
packFolder = cmd.getOptionValue("pack-folder") ?: packFolder packFolder = cmd.getOptionValue("pack-folder") ?: packFolder
manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile
} }
@@ -84,6 +92,7 @@ class Main(args: Array<String>) {
companion object { companion object {
// Called by packwiz-installer-bootstrap to set up the help command // Called by packwiz-installer-bootstrap to set up the help command
@JvmStatic
fun addNonBootstrapOptions(options: Options) { fun addNonBootstrapOptions(options: Options) {
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)") options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)")
options.addOption(null, "title", true, "Title of the installer window") options.addOption(null, "title", true, "Title of the installer window")
@@ -92,6 +101,7 @@ class Main(args: Array<String>) {
} }
// TODO: link these somehow so they're only defined once? // TODO: link these somehow so they're only defined once?
@JvmStatic
private fun addBootstrapOptions(options: Options) { private fun addBootstrapOptions(options: Options) {
options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates") options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates")
options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories") options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories")
@@ -109,11 +119,13 @@ class Main(args: Array<String>) {
startup(args) startup(args)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
EventQueue.invokeLater { if (guiEnabled) {
JOptionPane.showMessageDialog(null, EventQueue.invokeLater {
JOptionPane.showMessageDialog(null,
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message, "A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
"packwiz-installer", JOptionPane.ERROR_MESSAGE) "packwiz-installer", JOptionPane.ERROR_MESSAGE)
exitProcess(1) exitProcess(1)
}
} }
// In case the EventQueue is broken, exit after 1 minute // In case the EventQueue is broken, exit after 1 minute
Thread.sleep(60 * 1000.toLong()) Thread.sleep(60 * 1000.toLong())

View File

@@ -126,7 +126,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Loading pack file...")) ui.submitProgress(InstallProgress("Loading pack file..."))
val packFileSource = try { val packFileSource = try {
Objects.requireNonNull(opts.downloadURI)
val src = getFileSource(opts.downloadURI!!) val src = getFileSource(opts.downloadURI!!)
getHasher("sha256").getHashingSource(src) getHasher("sha256").getHashingSource(src)
} catch (e: Exception) { } catch (e: Exception) {
@@ -191,12 +190,16 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
handleCancellation() handleCancellation()
} }
try { try {
// This is badly written, I'll probably heavily refactor it at some point val index = pf.index!!
// The port to Kotlin made this REALLY messy!!!! getNewLoc(opts.downloadURI, index.file)?.let { newLoc ->
getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.index)!!.file)?.let { index.hashFormat?.let { hashFormat ->
pf.index!!.hashFormat?.let { it1 -> processIndex(
processIndex(it, newLoc,
getHash(Objects.requireNonNull(pf.index!!.hashFormat)!!, Objects.requireNonNull(pf.index!!.hash)!!), it1, manifest, invalidatedUris) getHash(index.hashFormat!!, index.hash!!),
hashFormat,
manifest,
invalidatedUris
)
} }
} }
} catch (e1: Exception) { } catch (e1: Exception) {

View File

@@ -10,7 +10,7 @@ class Murmur2Hasher : IHasher {
val tempBuffer = Buffer() val tempBuffer = Buffer()
override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) { override val hash: Hash by lazy(LazyThreadSafetyMode.NONE) {
val data = computeNormalizedArray(internalBuffer.readByteArray()) val data = internalBuffer.readByteArray()
Murmur2Hash(Murmur2Lib.hash32(data, data.size, 1)) Murmur2Hash(Murmur2Lib.hash32(data, data.size, 1))
} }
@@ -19,27 +19,42 @@ class Murmur2Hasher : IHasher {
val out = delegate.read(tempBuffer, byteCount) val out = delegate.read(tempBuffer, byteCount)
if (out > -1) { if (out > -1) {
sink.write(tempBuffer.clone(), out) sink.write(tempBuffer.clone(), out)
internalBuffer.write(tempBuffer, out) computeNormalizedBufferFaster(tempBuffer, internalBuffer)
} }
return out return out
} }
// Credit to https://github.com/modmuss50/CAV2/blob/master/murmur.go // Credit to https://github.com/modmuss50/CAV2/blob/master/murmur.go
private fun computeNormalizedArray(input: ByteArray): ByteArray { // private fun computeNormalizedArray(input: ByteArray): ByteArray {
val output = ByteArray(input.size) // val output = ByteArray(input.size)
// var index = 0
// for (b in input) {
// when (b) {
// 9.toByte(), 10.toByte(), 13.toByte(), 32.toByte() -> {}
// else -> {
// output[index] = b
// index++
// }
// }
// }
// val outputTrimmed = ByteArray(index)
// System.arraycopy(output, 0, outputTrimmed, 0, index)
// return outputTrimmed
// }
private fun computeNormalizedBufferFaster(input: Buffer, output: Buffer) {
var index = 0 var index = 0
for (b in input) { val arr = input.readByteArray()
when (b.toInt()) { for (b in arr) {
9, 10, 13, 32 -> {} when (b) {
9.toByte(), 10.toByte(), 13.toByte(), 32.toByte() -> {}
else -> { else -> {
output[index] = b arr[index] = b
index++ index++
} }
} }
} }
val outputTrimmed = ByteArray(index) output.write(arr, 0, index)
System.arraycopy(output, 0, outputTrimmed, 0, index)
return outputTrimmed
} }
} }

View File

@@ -1,6 +1,7 @@
package link.infra.packwiz.installer.request package link.infra.packwiz.installer.request
import link.infra.packwiz.installer.metadata.SpaceSafeURI import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.handlers.RequestHandlerFile
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP
import okio.Source import okio.Source
@@ -9,7 +10,8 @@ object HandlerManager {
private val handlers: List<IRequestHandler> = listOf( private val handlers: List<IRequestHandler> = listOf(
RequestHandlerGithub(), RequestHandlerGithub(),
RequestHandlerHTTP() RequestHandlerHTTP(),
RequestHandlerFile()
) )
@JvmStatic @JvmStatic

View File

@@ -0,0 +1,18 @@
package link.infra.packwiz.installer.request.handlers
import link.infra.packwiz.installer.metadata.SpaceSafeURI
import link.infra.packwiz.installer.request.IRequestHandler
import okio.Source
import okio.source
import java.nio.file.Paths
open class RequestHandlerFile : IRequestHandler {
override fun matchesHandler(loc: SpaceSafeURI): Boolean {
return "file" == loc.scheme
}
override fun getFileSource(loc: SpaceSafeURI): Source? {
val path = Paths.get(loc.toURL().toURI())
return path.source()
}
}

View File

@@ -65,7 +65,7 @@ class RequestHandlerGithub : RequestHandlerZip(true) {
if (!("http" == scheme || "https" == scheme)) { if (!("http" == scheme || "https" == scheme)) {
return false return false
} }
return "github.com" == loc.host // TODO: more match testing?
// TODO: sanity checks, support for more github urls return "github.com" == loc.host && branchMatcherPattern.matcher(loc.path).matches()
} }
} }

View File

@@ -10,10 +10,10 @@ import javax.swing.border.EmptyBorder
import kotlin.system.exitProcess import kotlin.system.exitProcess
class InstallWindow : IUserInterface { class InstallWindow : IUserInterface {
private val frmPackwizlauncher: JFrame private lateinit var frmPackwizlauncher: JFrame
private val lblProgresslabel: JLabel private lateinit var lblProgresslabel: JLabel
private val progressBar: JProgressBar private lateinit var progressBar: JProgressBar
private val btnOptions: JButton private lateinit var btnOptions: JButton
private var inputStateHandler: InputStateHandler? = null private var inputStateHandler: InputStateHandler? = null
private var title = "Updating modpack..." private var title = "Updating modpack..."
@@ -23,55 +23,57 @@ class InstallWindow : IUserInterface {
// TODO: separate JFrame junk from IUserInterface junk? // TODO: separate JFrame junk from IUserInterface junk?
init { init {
frmPackwizlauncher = JFrame().apply { EventQueue.invokeAndWait {
title = this@InstallWindow.title frmPackwizlauncher = JFrame().apply {
setBounds(100, 100, 493, 95) title = this@InstallWindow.title
defaultCloseOperation = JFrame.EXIT_ON_CLOSE setBounds(100, 100, 493, 95)
setLocationRelativeTo(null) defaultCloseOperation = JFrame.EXIT_ON_CLOSE
setLocationRelativeTo(null)
// Progress bar and loading text // Progress bar and loading text
add(JPanel().apply { add(JPanel().apply {
border = EmptyBorder(10, 10, 10, 10) border = EmptyBorder(10, 10, 10, 10)
layout = BorderLayout(0, 0) layout = BorderLayout(0, 0)
progressBar = JProgressBar().apply { progressBar = JProgressBar().apply {
isIndeterminate = true isIndeterminate = true
}
add(progressBar, BorderLayout.CENTER)
lblProgresslabel = JLabel("Loading...")
add(lblProgresslabel, BorderLayout.SOUTH)
}, BorderLayout.CENTER)
// Buttons
add(JPanel().apply {
border = EmptyBorder(0, 5, 0, 5)
layout = GridBagLayout()
btnOptions = JButton("Optional mods...").apply {
alignmentX = Component.CENTER_ALIGNMENT
addActionListener {
text = "Loading..."
isEnabled = false
inputStateHandler?.pressOptionsButton()
} }
} add(progressBar, BorderLayout.CENTER)
add(btnOptions, GridBagConstraints().apply {
gridx = 0
gridy = 0
})
add(JButton("Cancel").apply { lblProgresslabel = JLabel("Loading...")
addActionListener { add(lblProgresslabel, BorderLayout.SOUTH)
isEnabled = false }, BorderLayout.CENTER)
inputStateHandler?.pressCancelButton()
// Buttons
add(JPanel().apply {
border = EmptyBorder(0, 5, 0, 5)
layout = GridBagLayout()
btnOptions = JButton("Optional mods...").apply {
alignmentX = Component.CENTER_ALIGNMENT
addActionListener {
text = "Loading..."
isEnabled = false
inputStateHandler?.pressOptionsButton()
}
} }
}, GridBagConstraints().apply { add(btnOptions, GridBagConstraints().apply {
gridx = 0 gridx = 0
gridy = 1 gridy = 0
}) })
}, BorderLayout.EAST)
add(JButton("Cancel").apply {
addActionListener {
isEnabled = false
inputStateHandler?.pressCancelButton()
}
}, GridBagConstraints().apply {
gridx = 0
gridy = 1
})
}, BorderLayout.EAST)
}
} }
} }