This commit is contained in:
huangyuhui
2017-08-02 00:33:24 +08:00
parent 3c32ef39c7
commit 41498b5d93
20 changed files with 854 additions and 79 deletions

View File

@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.then
import java.net.Proxy
class DefaultDependencyManager(override val repository: DefaultGameRepository, override val downloadProvider: DownloadProvider, val proxy: Proxy = Proxy.NO_PROXY)
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, val proxy: Proxy = Proxy.NO_PROXY)
: AbstractDependencyManager(repository) {
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)

View File

@@ -35,4 +35,6 @@ class EventBus {
channel(obj.javaClass).fireEvent(obj)
}
}
}
val EVENT_BUS = EventBus()

View File

@@ -17,30 +17,43 @@
*/
package org.jackhuang.hmcl.event
import org.jackhuang.hmcl.util.SimpleMultimap
import java.util.*
class EventManager<T : EventObject> {
private val handlers = EnumMap<EventPriority, MutableList<(T) -> Unit>>(EventPriority::class.java).apply {
for (value in EventPriority.values())
put(value, LinkedList<(T) -> Unit>())
}
private val handlers = SimpleMultimap<EventPriority, (T) -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
private val handlers2 = SimpleMultimap<EventPriority, () -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
fun register(func: (T) -> Unit, priority: EventPriority = EventPriority.NORMAL) {
if (!handlers[priority]!!.contains(func))
handlers[priority]!!.add(func)
if (!handlers[priority].contains(func))
handlers.put(priority, func)
}
fun register(func: () -> Unit, priority: EventPriority = EventPriority.NORMAL) {
if (!handlers2[priority].contains(func))
handlers2.put(priority, func)
}
fun unregister(func: (T) -> Unit) {
EventPriority.values().forEach { handlers[it]!!.remove(func) }
handlers.remove(func)
}
fun unregister(func: () -> Unit) {
handlers2.remove(func)
}
fun fireEvent(event: T) {
for (priority in EventPriority.values())
for (handler in handlers[priority]!!)
for (priority in EventPriority.values()) {
for (handler in handlers[priority])
handler(event)
for (handler in handlers2[priority])
handler()
}
}
operator fun plusAssign(func: (T) -> Unit) = register(func)
operator fun plusAssign(func: () -> Unit) = register(func)
operator fun minusAssign(func: (T) -> Unit) = unregister(func)
operator fun minusAssign(func: () -> Unit) = unregister(func)
operator fun invoke(event: T) = fireEvent(event)
}

View File

@@ -0,0 +1,51 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.event
import java.util.EventObject
/**
* This event gets fired when loading versions in a .minecraft folder.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
* *
* @param IMinecraftService .minecraft folder.
* *
* @author huangyuhui
*/
class RefreshingVersionsEvent(source: Any) : EventObject(source)
/**
* This event gets fired when all the versions in .minecraft folder are loaded.
* <br>
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
* @param source [org.jackhuang.hmcl.game.GameRepository]
* @author huangyuhui
*/
class RefreshedVersionsEvent(source: Any) : EventObject(source)
/**
* This event gets fired when a minecraft version has been loaded.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
* @param version the version id.
* @author huangyuhui
*/
class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(source)

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.game
import com.google.gson.JsonSyntaxException
import org.jackhuang.hmcl.event.*
import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.util.fromJson
@@ -27,7 +28,7 @@ import java.io.IOException
import java.util.*
import java.util.logging.Level
open class DefaultGameRepository(val baseDirectory: File): GameRepository {
open class DefaultGameRepository(var baseDirectory: File): GameRepository {
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
override fun hasVersion(id: String) = versions.containsKey(id)
@@ -81,9 +82,8 @@ open class DefaultGameRepository(val baseDirectory: File): GameRepository {
return file.deleteRecursively()
}
protected open fun refreshVersionsImpl() {
@Synchronized
override fun refreshVersions() {
versions.clear()
if (ClassicVersion.hasClassicVersion(baseDirectory)) {
@@ -123,7 +123,16 @@ open class DefaultGameRepository(val baseDirectory: File): GameRepository {
}
versions[id] = version
EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id))
}
}
@Synchronized
final override fun refreshVersions() {
EVENT_BUS.fireEvent(RefreshingVersionsEvent(this))
refreshVersionsImpl()
EVENT_BUS.fireEvent(RefreshedVersionsEvent(this))
}
override fun getAssetIndex(assetId: String): AssetIndex {

View File

@@ -95,6 +95,14 @@ interface GameRepository : VersionProvider {
*/
fun getVersionJar(version: Version): File
/**
* Get minecraft jar
*
* @param version version id
* @return the minecraft jar
*/
fun getVersionJar(version: String): File = getVersionJar(getVersion(version).resolve(this))
/**
* Rename given version to new name.
*

View File

@@ -0,0 +1,139 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.util.closeQuietly
import org.jackhuang.hmcl.util.readFullyAsByteArray
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.io.File
private fun lessThan32(b: ByteArray, x: Int): Int {
var x = x
while (x < b.size) {
if (b[x] < 32)
return x
x++
}
return -1
}
fun matchArray(a: ByteArray, b: ByteArray): Int {
for (i in 0..a.size - b.size - 1) {
var j = 1
for (k in b.indices) {
if (b[k] == a[i + k])
continue
j = 0
break
}
if (j != 0)
return i
}
return -1
}
@Throws(IOException::class)
private fun getVersionOfOldMinecraft(file: ZipFile, entry: ZipEntry): String? {
val tmp = file.getInputStream(entry).readFullyAsByteArray()
val bytes = "Minecraft Minecraft ".toByteArray(Charsets.US_ASCII)
var j = matchArray(tmp, bytes)
if (j < 0) {
return null
}
val i = j + bytes.size
j = lessThan32(tmp, i)
if (j < 0) {
return null
}
val ver = String(tmp, i, j - i, Charsets.US_ASCII)
return ver
}
@Throws(IOException::class)
private fun getVersionOfNewMinecraft(file: ZipFile, entry: ZipEntry): String? {
val tmp = file.getInputStream(entry).readFullyAsByteArray()
var str = "-server.txt".toByteArray(charset("ASCII"))
var j = matchArray(tmp, str)
if (j < 0) {
return null
}
var i = j + str.size
i += 11
j = lessThan32(tmp, i)
if (j < 0) {
return null
}
val result = String(tmp, i, j - i, Charsets.US_ASCII)
val ch = result[0]
// 1.8.1+
if (ch < '0' || ch > '9') {
str = "Can't keep up! Did the system time change, or is the server overloaded?".toByteArray(charset("ASCII"))
j = matchArray(tmp, str)
if (j < 0) {
return null
}
i = -1
while (j > 0) {
if (tmp[j] in 48..57) {
i = j
break
}
j--
}
if (i == -1) {
return null
}
var k = i
if (tmp[i + 1] >= 'a'.toInt() && tmp[i + 1] <= 'z'.toInt())
i++
while (tmp[k] in 48..57 || tmp[k] == '-'.toByte() || tmp[k] == '.'.toByte() || tmp[k] >= 97 && tmp[k] <= 'z'.toByte())
k--
k++
return String(tmp, k, i - k + 1, Charsets.US_ASCII)
}
return result
}
fun minecraftVersion(file: File?): String? {
if (file == null || !file.isFile || !file.canRead()) {
return null
}
var f: ZipFile? = null
try {
f = ZipFile(file)
val minecraft = f
.getEntry("net/minecraft/client/Minecraft.class")
if (minecraft != null)
return getVersionOfOldMinecraft(f, minecraft)
val main = f.getEntry("net/minecraft/client/main/Main.class")
val minecraftserver = f.getEntry("net/minecraft/server/MinecraftServer.class")
if (main != null && minecraftserver != null)
return getVersionOfNewMinecraft(f, minecraftserver)
return null
} catch (e: IOException) {
return null
} finally {
f?.closeQuietly()
}
}

View File

@@ -28,6 +28,7 @@ import com.google.gson.TypeAdapter
import com.google.gson.Gson
import com.google.gson.TypeAdapterFactory
import org.jackhuang.hmcl.game.Library
import java.io.File
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
@@ -40,6 +41,8 @@ val GSON: Gson = GsonBuilder()
.registerTypeAdapter(Library::class.java, Library)
.registerTypeAdapter(Date::class.java, DateTypeAdapter)
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
.registerTypeAdapter(Platform::class.java, Platform)
.registerTypeAdapter(File::class.java, FileTypeAdapter)
.registerTypeAdapterFactory(ValidationTypeAdapterFactory)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory)
.create()
@@ -48,6 +51,14 @@ inline fun <reified T> typeOf(): Type = object : TypeToken<T>() {}.type
inline fun <reified T> Gson.fromJson(json: String): T? = fromJson<T>(json, T::class.java)
inline fun <reified T> Gson.fromJsonQuietly(json: String): T? {
try {
return fromJson<T>(json)
} catch (json: JsonParseException) {
return null
}
}
/**
* Check if the json object's fields automatically filled by Gson are in right format.
*/
@@ -189,4 +200,17 @@ object DateTypeAdapter : JsonSerializer<Date>, JsonDeserializer<Date> {
return result.substring(0, 22) + ":" + result.substring(22)
}
}
}
object FileTypeAdapter : JsonSerializer<File>, JsonDeserializer<File> {
override fun serialize(src: File?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
if (src == null) return JsonNull.INSTANCE
else return JsonPrimitive(src.path)
}
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): File? {
if (json == null) return null
else return File(json.asString)
}
}

View File

@@ -17,12 +17,14 @@
*/
package org.jackhuang.hmcl.util
import com.google.gson.annotations.SerializedName
import java.io.File
import java.io.IOException
import java.io.Serializable
import java.util.regex.Pattern
data class JavaVersion internal constructor(
@SerializedName("location")
val binary: File,
val version: Int,
val platform: Platform) : Serializable
@@ -74,8 +76,8 @@ data class JavaVersion internal constructor(
}
fun getJavaFile(home: File): File {
var path = home.resolve("bin")
var javaw = path.resolve("javaw.exe")
val path = home.resolve("bin")
val javaw = path.resolve("javaw.exe")
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
return javaw
else

View File

@@ -17,12 +17,33 @@
*/
package org.jackhuang.hmcl.util
import com.google.gson.*
import java.lang.reflect.Type
enum class Platform(val bit: String) {
BIT_32("32"),
BIT_64("64"),
UNKNOWN("unknown");
companion object {
companion object Serializer: JsonSerializer<Platform>, JsonDeserializer<Platform> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Platform? {
if (json == null) return null
return when (json.asInt) {
0 -> BIT_32
1 -> BIT_64
else -> UNKNOWN
}
}
override fun serialize(src: Platform?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
if (src == null) return null
return when (src) {
BIT_32 -> JsonPrimitive(0)
BIT_64 -> JsonPrimitive(1)
UNKNOWN -> JsonPrimitive(-1)
}
}
val PLATFORM: Platform by lazy {
if (IS_64_BIT) BIT_64 else BIT_32

View File

@@ -40,13 +40,19 @@ class SimpleMultimap<K, V>(val maper: () -> MutableMap<K, Collection<V>>, val va
valuesImpl += value
}
fun remove(key: K): Collection<V>? {
fun removeAll(key: K): Collection<V>? {
val result = map.remove(key)
if (result != null)
valuesImpl.removeAll(result)
return result
}
fun remove(value: V) {
map.values.forEach {
it.remove(value)
}
}
fun clear() {
map.clear()
valuesImpl.clear()