LICENSE
This commit is contained in:
28
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt
Normal file
28
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
import java.net.Proxy
|
||||
|
||||
abstract class Account() {
|
||||
abstract val username: String
|
||||
@Throws(AuthenticationException::class)
|
||||
abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo
|
||||
abstract fun logOut()
|
||||
abstract fun toStorage(): Map<out Any, Any>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
interface AccountFactory<T : Account> {
|
||||
fun fromUsername(username: String, password: String = ""): T
|
||||
fun fromStorage(storage: Map<Any, Any>): T
|
||||
}
|
||||
30
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.kt
Normal file
30
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
|
||||
@Immutable
|
||||
data class AuthInfo(
|
||||
val username: String,
|
||||
val userId: String,
|
||||
val authToken: String,
|
||||
val userType: UserType = UserType.LEGACY,
|
||||
val userProperties: String = "{}",
|
||||
val userPropertyMap: String = "{}"
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
class AuthenticationException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
import org.jackhuang.hmcl.util.DigestUtils
|
||||
import java.net.Proxy
|
||||
|
||||
class OfflineAccount private constructor(val uuid: String, override val username: String): Account() {
|
||||
override fun logIn(proxy: Proxy): AuthInfo {
|
||||
if (username.isBlank() || uuid.isBlank())
|
||||
throw AuthenticationException("Username cannot be empty")
|
||||
return AuthInfo(
|
||||
username = username,
|
||||
userId = uuid,
|
||||
authToken = uuid
|
||||
)
|
||||
}
|
||||
|
||||
override fun logOut() {
|
||||
// Offline account need not log out.
|
||||
}
|
||||
|
||||
override fun toStorage(): Map<Any, Any> {
|
||||
return mapOf(
|
||||
"uuid" to uuid,
|
||||
"username" to username
|
||||
)
|
||||
}
|
||||
|
||||
companion object OfflineAccountFactory : AccountFactory<OfflineAccount> {
|
||||
|
||||
override fun fromUsername(username: String, password: String): OfflineAccount {
|
||||
return OfflineAccount(
|
||||
username = username,
|
||||
uuid = getUUIDFromUserName(username)
|
||||
)
|
||||
}
|
||||
|
||||
override fun fromStorage(storage: Map<Any, Any>): OfflineAccount {
|
||||
val username = storage["username"] as? String
|
||||
?: throw IllegalStateException("Configuration is malformed.")
|
||||
val obj = storage["uuid"]
|
||||
return OfflineAccount(
|
||||
username = username,
|
||||
uuid = obj as? String ?: getUUIDFromUserName(username)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getUUIDFromUserName(username: String) = DigestUtils.md5Hex(username)
|
||||
}
|
||||
}
|
||||
34
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.kt
Normal file
34
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
enum class UserType() {
|
||||
LEGACY,
|
||||
MOJANG;
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromName(name: String) = BY_NAME[name.toLowerCase()]
|
||||
fun fromLegacy(isLegacy: Boolean) = if (isLegacy) LEGACY else MOJANG
|
||||
|
||||
private val BY_NAME = HashMap<String, UserType>().apply {
|
||||
for (type in values())
|
||||
this[type.name.toLowerCase()] = type
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
data class AuthenticationRequest(
|
||||
val username: String,
|
||||
val password: String,
|
||||
val clientToken: String,
|
||||
val agent: Map<String, Any> = mapOf(
|
||||
"name" to "Minecraft",
|
||||
"version" to 1
|
||||
),
|
||||
val requestUser: Boolean = true
|
||||
) {
|
||||
}
|
||||
@@ -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.auth.yggdrasil
|
||||
|
||||
import com.google.gson.*
|
||||
import java.lang.reflect.Type
|
||||
import com.google.gson.JsonObject
|
||||
import java.util.UUID
|
||||
import com.google.gson.JsonParseException
|
||||
|
||||
data class GameProfile(
|
||||
val id: UUID? = null,
|
||||
val name: String? = null,
|
||||
val properties: PropertyMap = PropertyMap(),
|
||||
val legacy: Boolean = false
|
||||
) {
|
||||
companion object GameProfileSerializer: JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
|
||||
override fun serialize(src: GameProfile, typeOfSrc: Type?, context: JsonSerializationContext): JsonElement {
|
||||
val result = JsonObject()
|
||||
if (src.id != null)
|
||||
result.add("id", context.serialize(src.id))
|
||||
if (src.name != null)
|
||||
result.addProperty("name", src.name)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): GameProfile {
|
||||
if (json !is JsonObject)
|
||||
throw JsonParseException("The json element is not a JsonObject.")
|
||||
val id = if (json.has("id")) context.deserialize<UUID>(json.get("id"), UUID::class.java) else null
|
||||
val name = if (json.has("name")) json.getAsJsonPrimitive("name").asString else null
|
||||
return GameProfile(id, name)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
data class Property(val name: String, val value: String)
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
import com.google.gson.*
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import kotlin.collections.HashMap
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
|
||||
|
||||
|
||||
class PropertyMap: HashMap<String, Property>() {
|
||||
|
||||
fun toList(): List<Map<String, String>> {
|
||||
val properties = LinkedList<Map<String, String>>()
|
||||
values.forEach { (name, value) ->
|
||||
properties += mapOf(
|
||||
"name" to name,
|
||||
"value" to value
|
||||
)
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
/**
|
||||
* Load property map from list.
|
||||
* @param list Right type is List<Map<String, String>>. Using List<*> here because of fault tolerance
|
||||
*/
|
||||
fun fromList(list: List<*>) {
|
||||
list.forEach { propertyMap ->
|
||||
if (propertyMap is Map<*, *>) {
|
||||
val name = propertyMap["name"] as? String
|
||||
val value = propertyMap["value"] as? String
|
||||
if (name != null && value != null)
|
||||
put(name, Property(name, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object Serializer : JsonSerializer<PropertyMap>, JsonDeserializer<PropertyMap> {
|
||||
override fun serialize(src: PropertyMap, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||
val result = JsonArray()
|
||||
for ((name, value) in src.values)
|
||||
result.add(JsonObject().apply {
|
||||
addProperty("name", name)
|
||||
addProperty("value", value)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): PropertyMap {
|
||||
val result = PropertyMap()
|
||||
if (json is JsonObject) {
|
||||
for ((key, value) in json.entrySet())
|
||||
if (value is JsonArray)
|
||||
for (element in value)
|
||||
result.put(key, Property(key, element.asString))
|
||||
} else if (json is JsonArray)
|
||||
for (element in json)
|
||||
if (element is JsonObject) {
|
||||
val name = element.getAsJsonPrimitive("name").asString
|
||||
val value = element.getAsJsonPrimitive("value").asString
|
||||
result.put(name, Property(name, value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
data class RefreshRequest(
|
||||
val clientToken: String,
|
||||
val accessToken: String,
|
||||
val selectedProfile: GameProfile?,
|
||||
val requestUser: Boolean = true
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
class Response (
|
||||
val accessToken: String? = null,
|
||||
val clientToken: String? = null,
|
||||
val selectedProfile: GameProfile? = null,
|
||||
val availableProfiles: Array<GameProfile>? = null,
|
||||
val user: User? = null,
|
||||
val error: String? = null,
|
||||
val errorMessage: String? = null,
|
||||
val cause: String? = null
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import org.jackhuang.hmcl.util.Validation
|
||||
|
||||
class User (
|
||||
val id: String = "",
|
||||
val properties: PropertyMap? = null
|
||||
) : Validation {
|
||||
override fun validate() {
|
||||
if (id.isBlank())
|
||||
throw JsonParseException("User id cannot be empty")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
data class ValidateRequest (
|
||||
val clientToken: String = "",
|
||||
val accessToken: String = ""
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParseException
|
||||
import org.jackhuang.hmcl.auth.*
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.doGet
|
||||
import org.jackhuang.hmcl.util.doPost
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import java.io.IOException
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
|
||||
class YggdrasilAccount private constructor(override val username: String): Account() {
|
||||
private var password: String? = null
|
||||
private var userId: String? = null
|
||||
private var accessToken: String? = null
|
||||
private var clientToken: String = UUID.randomUUID().toString()
|
||||
private var isOnline: Boolean = false
|
||||
private var userProperties = PropertyMap()
|
||||
private var selectedProfile: GameProfile? = null
|
||||
private var profiles: Array<GameProfile>? = null
|
||||
private var userType: UserType = UserType.LEGACY
|
||||
|
||||
init {
|
||||
if (username.isBlank())
|
||||
throw IllegalArgumentException("Username cannot be blank")
|
||||
if (!username.contains("@"))
|
||||
throw IllegalArgumentException("Yggdrasil account user name must be email")
|
||||
}
|
||||
|
||||
val isLoggedIn: Boolean
|
||||
get() = isNotBlank(accessToken)
|
||||
|
||||
val canPlayOnline: Boolean
|
||||
get() = isLoggedIn && selectedProfile != null && isOnline
|
||||
|
||||
val canLogIn: Boolean
|
||||
get() = !canPlayOnline && username.isNotBlank() && (isNotBlank(password) || isNotBlank((accessToken)))
|
||||
|
||||
override fun logIn(proxy: Proxy): AuthInfo {
|
||||
if (canPlayOnline)
|
||||
return AuthInfo(
|
||||
username = selectedProfile!!.name!!,
|
||||
userId = UUIDTypeAdapter.fromUUID(selectedProfile!!.id!!),
|
||||
authToken = accessToken!!,
|
||||
userType = userType,
|
||||
userProperties = GSON.toJson(userProperties)
|
||||
)
|
||||
else {
|
||||
logIn0(proxy)
|
||||
if (!isLoggedIn)
|
||||
throw AuthenticationException("Wrong password for account $username")
|
||||
if (selectedProfile == null) {
|
||||
// TODO: multi-available-profiles support
|
||||
throw UnsupportedOperationException("Do not support multi-available-profiles account yet.")
|
||||
} else {
|
||||
return AuthInfo(
|
||||
username = selectedProfile!!.name!!,
|
||||
userId = UUIDTypeAdapter.fromUUID(selectedProfile!!.id!!),
|
||||
authToken = accessToken!!,
|
||||
userType = userType,
|
||||
userProperties = GSON.toJson(userProperties)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logIn0(proxy: Proxy) {
|
||||
if (isNotBlank(accessToken)) {
|
||||
if (isBlank(userId))
|
||||
if (isNotBlank(username))
|
||||
userId = username
|
||||
else
|
||||
throw AuthenticationException("Invalid uuid and username")
|
||||
if (checkTokenValidity(proxy)) {
|
||||
isOnline = true
|
||||
return
|
||||
}
|
||||
logIn1(ROUTE_REFRESH, RefreshRequest(accessToken!!, clientToken, selectedProfile), proxy)
|
||||
} else if (isNotBlank(password)) {
|
||||
logIn1(ROUTE_AUTHENTICATE, AuthenticationRequest(username, password!!, clientToken), proxy)
|
||||
} else
|
||||
throw AuthenticationException("Password cannot be blank")
|
||||
}
|
||||
|
||||
private fun logIn1(url: URL, input: Any, proxy: Proxy) {
|
||||
val response = makeRequest(url, input, proxy)
|
||||
|
||||
if (clientToken != response?.clientToken)
|
||||
throw AuthenticationException("Client token changed")
|
||||
|
||||
if (response.selectedProfile != null)
|
||||
userType = UserType.fromLegacy(response.selectedProfile.legacy)
|
||||
else if (response.availableProfiles?.getOrNull(0) != null)
|
||||
userType = UserType.fromLegacy(response.availableProfiles[0].legacy)
|
||||
|
||||
val user = response.user
|
||||
userId = user?.id ?: username
|
||||
isOnline = true
|
||||
profiles = response.availableProfiles
|
||||
selectedProfile = response.selectedProfile
|
||||
userProperties.clear()
|
||||
accessToken = response.accessToken
|
||||
|
||||
if (user != null && user.properties != null)
|
||||
userProperties.putAll(user.properties)
|
||||
}
|
||||
|
||||
override fun logOut() {
|
||||
password = null
|
||||
userId = null
|
||||
accessToken = null
|
||||
isOnline = false
|
||||
userProperties.clear()
|
||||
profiles = null
|
||||
selectedProfile = null
|
||||
}
|
||||
|
||||
override fun toStorage(): Map<out Any, Any> {
|
||||
val result = HashMap<String, Any>()
|
||||
|
||||
result[STORAGE_KEY_USER_NAME] = username
|
||||
if (userId != null)
|
||||
result[STORAGE_KEY_USER_ID] = userId!!
|
||||
if (!userProperties.isEmpty())
|
||||
result[STORAGE_KEY_USER_PROPERTIES] = userProperties.toList()
|
||||
val profile = selectedProfile
|
||||
if (profile != null && profile.name != null && profile.id != null) {
|
||||
result[STORAGE_KEY_PROFILE_NAME] = profile.name
|
||||
result[STORAGE_KEY_PROFILE_ID] = profile.id
|
||||
if (!profile.properties.isEmpty())
|
||||
result[STORAGE_KEY_PROFILE_PROPERTIES] = profile.properties.toList()
|
||||
}
|
||||
if (accessToken?.isNotBlank() ?: false)
|
||||
result[STORAGE_KEY_ACCESS_TOKEN] = accessToken!!
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun makeRequest(url: URL, input: Any?, proxy: Proxy): Response? {
|
||||
try {
|
||||
val jsonResult =
|
||||
if (input == null) url.doGet(proxy)
|
||||
else url.doPost(GSON.toJson(input), "application/json", proxy)
|
||||
|
||||
val response = GSON.fromJson<Response>(jsonResult) ?: return null
|
||||
|
||||
if (response.error?.isNotBlank() ?: false) {
|
||||
LOG.severe("Failed to log in, the auth server returned an error: " + response.error + ", message: " + response.errorMessage + ", cause: " + response.cause)
|
||||
throw AuthenticationException("Request error: ${response.errorMessage}")
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (e: IOException) {
|
||||
throw AuthenticationException("Unable to connect to authentication server", e)
|
||||
} catch (e: JsonParseException) {
|
||||
throw AuthenticationException("Unable to parse server response", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTokenValidity(proxy: Proxy): Boolean {
|
||||
val access = accessToken
|
||||
try {
|
||||
if (access == null)
|
||||
return false
|
||||
makeRequest(ROUTE_VALIDATE, ValidateRequest(clientToken, access), proxy)
|
||||
return true
|
||||
} catch (e: AuthenticationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
companion object YggdrasilAccountFactory : AccountFactory<YggdrasilAccount> {
|
||||
private val GSON = GsonBuilder()
|
||||
.registerTypeAdapter(GameProfile::class.java, GameProfile)
|
||||
.registerTypeAdapter(PropertyMap::class.java, PropertyMap)
|
||||
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
|
||||
.create()
|
||||
|
||||
private val BASE_URL = "https://authserver.mojang.com/"
|
||||
private val ROUTE_AUTHENTICATE = (BASE_URL + "authenticate").toURL()
|
||||
private val ROUTE_REFRESH = (BASE_URL + "refresh").toURL()
|
||||
private val ROUTE_VALIDATE = (BASE_URL + "validate").toURL()
|
||||
|
||||
private val STORAGE_KEY_ACCESS_TOKEN = "accessToken"
|
||||
private val STORAGE_KEY_PROFILE_NAME = "displayName"
|
||||
private val STORAGE_KEY_PROFILE_ID = "uuid"
|
||||
private val STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties"
|
||||
private val STORAGE_KEY_USER_NAME = "username"
|
||||
private val STORAGE_KEY_USER_ID = "userid"
|
||||
private val STORAGE_KEY_USER_PROPERTIES = "userProperties"
|
||||
|
||||
override fun fromUsername(username: String, password: String): YggdrasilAccount {
|
||||
val account = YggdrasilAccount(username)
|
||||
account.password = password
|
||||
return account
|
||||
}
|
||||
|
||||
override fun fromStorage(storage: Map<Any, Any>): YggdrasilAccount {
|
||||
val username = storage[STORAGE_KEY_USER_NAME] as? String ?: throw IllegalArgumentException("storage does not have key $STORAGE_KEY_USER_NAME")
|
||||
val account = YggdrasilAccount(username)
|
||||
account.userId = storage[STORAGE_KEY_USER_ID] as? String ?: username
|
||||
account.accessToken = storage[STORAGE_KEY_ACCESS_TOKEN] as? String
|
||||
val userProperties = storage[STORAGE_KEY_USER_PROPERTIES] as? List<*>
|
||||
if (userProperties != null)
|
||||
account.userProperties.fromList(userProperties)
|
||||
val profileId = storage[STORAGE_KEY_PROFILE_ID] as? String
|
||||
val profileName = storage[STORAGE_KEY_PROFILE_NAME] as? String
|
||||
val profile: GameProfile?
|
||||
if (profileId != null && profileName != null) {
|
||||
profile = GameProfile(UUIDTypeAdapter.fromString(profileId), profileName)
|
||||
val profileProperties = storage[STORAGE_KEY_PROFILE_PROPERTIES] as? List<*>
|
||||
if (profileProperties != null)
|
||||
profile.properties.fromList(profileProperties)
|
||||
} else
|
||||
profile = null
|
||||
account.selectedProfile = profile
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.game.*
|
||||
|
||||
abstract class AbstractDependencyManager(repository: GameRepository)
|
||||
: DependencyManager(repository) {
|
||||
abstract val downloadProvider: DownloadProvider
|
||||
|
||||
fun getVersions(id: String, selfVersion: String) =
|
||||
downloadProvider.getVersionListById(id).getVersions(selfVersion)
|
||||
|
||||
override fun getVersionList(id: String): VersionList<*> {
|
||||
return downloadProvider.getVersionListById(id)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList
|
||||
|
||||
object BMCLAPIDownloadProvider : DownloadProvider() {
|
||||
override val libraryBaseURL: String = "http://bmclapi2.bangbang93.com/libraries/"
|
||||
override val versionListURL: String = "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json"
|
||||
override val versionBaseURL: String = "http://bmclapi2.bangbang93.com/versions/"
|
||||
override val assetIndexBaseURL: String = "http://bmclapi2.bangbang93.com/indexes/"
|
||||
override val assetBaseURL: String = "http://bmclapi2.bangbang93.com/assets/"
|
||||
|
||||
override fun getVersionListById(id: String): VersionList<*> {
|
||||
return when(id) {
|
||||
"game" -> GameVersionList
|
||||
"forge" -> ForgeVersionList
|
||||
"liteloader" -> LiteLoaderVersionList
|
||||
"optifine" -> OptiFineBMCLVersionList
|
||||
else -> throw IllegalArgumentException("Unrecognized version list id: $id")
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectURL(baseURL: String): String = baseURL
|
||||
.replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com")
|
||||
.replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com")
|
||||
.replace("https://libraries.minecraft.net", "http://bmclapi2.bangbang93.com/libraries")
|
||||
.replace("http://files.minecraftforge.net/maven", "http://bmclapi2.bangbang93.com/maven")
|
||||
.replace("http://dl.liteloader.com/versions/versions.json", "http://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
|
||||
.replace("http://dl.liteloader.com/versions", "http://bmclapi2.bangbang93.com/maven")
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask
|
||||
import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask
|
||||
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.ParallelTask
|
||||
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)
|
||||
: AbstractDependencyManager(repository) {
|
||||
|
||||
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)
|
||||
|
||||
override fun checkGameCompletionAsync(version: Version): Task {
|
||||
val tasks: Array<Task> = arrayOf(
|
||||
GameAssetDownloadTask(this, version),
|
||||
GameLoggingDownloadTask(this, version),
|
||||
GameLibrariesTask(this, version)
|
||||
)
|
||||
return ParallelTask(*tasks)
|
||||
}
|
||||
|
||||
override fun installLibraryAsync(version: Version, libraryId: String, libraryVersion: String): Task {
|
||||
if (libraryId == "forge")
|
||||
return ForgeInstallTask(this, version, libraryVersion) then { task ->
|
||||
val newVersion = task.result!!
|
||||
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
|
||||
}
|
||||
else if (libraryId == "liteloader")
|
||||
return LiteLoaderInstallTask(this, version, libraryVersion) then { task ->
|
||||
val newVersion = task.result!!
|
||||
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
|
||||
}
|
||||
else if (libraryId == "optifine")
|
||||
return OptiFineInstallTask(this, version, libraryVersion) then { task ->
|
||||
val newVersion = task.result!!
|
||||
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
|
||||
}
|
||||
else
|
||||
throw IllegalArgumentException("Library id $libraryId is unrecognized.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask
|
||||
import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask
|
||||
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.task.*
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.util.*
|
||||
|
||||
class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameBuilder() {
|
||||
val repository = dependencyManager.repository
|
||||
val downloadProvider = dependencyManager.downloadProvider
|
||||
|
||||
override fun buildAsync(): Task {
|
||||
return VersionJSONDownloadTask(gameVersion = gameVersion) then { task ->
|
||||
var version = GSON.fromJson<Version>(task.result!!)
|
||||
version = version.copy(jar = version.id, id = name)
|
||||
var result = ParallelTask(
|
||||
GameAssetDownloadTask(dependencyManager, version),
|
||||
GameLoggingDownloadTask(dependencyManager, version),
|
||||
GameDownloadTask(version),
|
||||
GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
) then VersionJSONSaveTask(dependencyManager, version)
|
||||
|
||||
if (toolVersions.containsKey("forge"))
|
||||
result = result then libraryTaskHelper(version, "forge")
|
||||
if (toolVersions.containsKey("liteloader"))
|
||||
result = result then libraryTaskHelper(version, "liteloader")
|
||||
if (toolVersions.containsKey("optifine"))
|
||||
result = result then libraryTaskHelper(version, "optifine")
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun libraryTaskHelper(version: Version, libraryId: String): Task.(Task) -> Task = { prev ->
|
||||
var thisVersion = version
|
||||
if (prev is TaskResult<*> && prev.result is Version) {
|
||||
thisVersion = prev.result as Version
|
||||
}
|
||||
dependencyManager.installLibraryAsync(thisVersion, libraryId, toolVersions[libraryId]!!)
|
||||
}
|
||||
|
||||
inner class VersionJSONDownloadTask(val gameVersion: String): Task() {
|
||||
override val dependents: MutableCollection<Task> = LinkedList()
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
var httpTask: GetTask? = null
|
||||
val result: String? get() = httpTask?.result
|
||||
|
||||
val gameVersionList: VersionList<*> = dependencyManager.getVersionList("game")
|
||||
init {
|
||||
if (!gameVersionList.loaded)
|
||||
dependents += gameVersionList.refreshAsync(downloadProvider)
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val remoteVersion = gameVersionList.getVersions(gameVersion).firstOrNull()
|
||||
?: throw Error("Cannot find specific version $gameVersion in remote repository")
|
||||
|
||||
val jsonURL = downloadProvider.injectURL(remoteVersion.url)
|
||||
httpTask = GetTask(jsonURL.toURL(), proxy = dependencyManager.proxy)
|
||||
dependencies += httpTask!!
|
||||
}
|
||||
}
|
||||
|
||||
inner class GameDownloadTask(var version: Version) : Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override fun execute() {
|
||||
val jar = repository.getVersionJar(version)
|
||||
|
||||
dependencies += FileDownloadTask(
|
||||
url = downloadProvider.injectURL(version.download.url).toURL(),
|
||||
file = jar,
|
||||
hash = version.download.sha1,
|
||||
proxy = dependencyManager.proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
|
||||
abstract class DependencyManager(open val repository: GameRepository) {
|
||||
|
||||
/**
|
||||
* Check if the game is complete.
|
||||
* Check libraries, assets, logging files and so on.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
abstract fun checkGameCompletionAsync(version: Version): Task
|
||||
|
||||
/**
|
||||
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.
|
||||
*/
|
||||
abstract fun gameBuilder(): GameBuilder
|
||||
|
||||
abstract fun installLibraryAsync(version: Version, libraryId: String, libraryVersion: String): Task
|
||||
|
||||
/**
|
||||
* Get registered version list.
|
||||
* @param id the id of version list. i.e. game, forge, liteloader, optifine
|
||||
* @throws IllegalArgumentException if the version list of specific id is not found.
|
||||
*/
|
||||
abstract fun getVersionList(id: String): VersionList<*>
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
|
||||
abstract class DownloadProvider {
|
||||
abstract val libraryBaseURL: String
|
||||
abstract val versionListURL: String
|
||||
abstract val versionBaseURL: String
|
||||
abstract val assetIndexBaseURL: String
|
||||
abstract val assetBaseURL: String
|
||||
abstract fun injectURL(baseURL: String): String
|
||||
abstract fun getVersionListById(id: String): VersionList<*>
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.game.SimpleVersionProvider
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.task.then
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String) : TaskResult<Version>() {
|
||||
private val forgeVersionList = dependencyManager.getVersionList("forge")
|
||||
private val installer: File = File("forge-installer.jar").absoluteFile
|
||||
lateinit var remote: RemoteVersion<*>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
|
||||
init {
|
||||
if (version.jar == null)
|
||||
throw IllegalArgumentException()
|
||||
if (!forgeVersionList.loaded)
|
||||
dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider) then {
|
||||
remote = forgeVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote forge version ${version.jar}, $remoteVersion not found")
|
||||
FileDownloadTask(remote.url.toURL(), installer)
|
||||
}
|
||||
else {
|
||||
remote = forgeVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote forge version ${version.jar}, $remoteVersion not found")
|
||||
dependents += FileDownloadTask(remote.url.toURL(), installer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
ZipFile(installer).use { zipFile ->
|
||||
val installProfile = GSON.fromJson<InstallProfile>(zipFile.getInputStream(zipFile.getEntry("install_profile.json")).readFullyAsString())
|
||||
|
||||
// unpack the universal jar in the installer file.
|
||||
|
||||
val forgeLibrary = Library.fromName(installProfile.install!!.path!!)
|
||||
val forgeFile = dependencyManager.repository.getLibraryFile(version, forgeLibrary)
|
||||
if (!forgeFile.makeFile())
|
||||
throw IOException("Cannot make directory ${forgeFile.parentFile}")
|
||||
|
||||
val forgeEntry = zipFile.getEntry(installProfile.install.filePath)
|
||||
zipFile.getInputStream(forgeEntry).copyToAndClose(forgeFile.outputStream())
|
||||
|
||||
// resolve the version
|
||||
val versionProvider = SimpleVersionProvider()
|
||||
versionProvider.addVersion(version)
|
||||
|
||||
result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id)
|
||||
dependencies += GameLibrariesTask(dependencyManager, installProfile.versionInfo)
|
||||
}
|
||||
|
||||
check(installer.delete(), { "Unable to delete installer file $installer" })
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import org.jackhuang.hmcl.util.Validation
|
||||
|
||||
@Immutable
|
||||
internal class ForgeVersion (
|
||||
val branch: String? = null,
|
||||
val mcversion: String? = null,
|
||||
val jobver: String? = null,
|
||||
val version: String? = null,
|
||||
val build: Int = 0,
|
||||
val modified: Double = 0.0,
|
||||
val files: Array<Array<String>>? = null
|
||||
) : Validation {
|
||||
override fun validate() {
|
||||
check(files != null, { "ForgeVersion files cannot be null" })
|
||||
check(version != null, { "ForgeVersion version cannot be null" })
|
||||
check(mcversion != null, { "ForgeVersion mcversion cannot be null" })
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal class ForgeVersionRoot (
|
||||
val artifact: String? = null,
|
||||
val webpath: String? = null,
|
||||
val adfly: String? = null,
|
||||
val homepage: String? = null,
|
||||
val name: String? = null,
|
||||
val branches: Map<String, Array<Int>>? = null,
|
||||
val mcversion: Map<String, Array<Int>>? = null,
|
||||
val promos: Map<String, Int>? = null,
|
||||
val number: Map<Int, ForgeVersion>? = null
|
||||
) : Validation {
|
||||
override fun validate() {
|
||||
check(number != null, { "ForgeVersionRoot number cannot be null" })
|
||||
check(mcversion != null, { "ForgeVersionRoot mcversion cannot be null" })
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class Install (
|
||||
val profileName: String? = null,
|
||||
val target: String? = null,
|
||||
val path: String? = null,
|
||||
val version: String? = null,
|
||||
val filePath: String? = null,
|
||||
val welcome: String? = null,
|
||||
val minecraft: String? = null,
|
||||
val mirrorList: String? = null,
|
||||
val logo: String? = null
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal data class InstallProfile (
|
||||
@SerializedName("install")
|
||||
val install: Install? = null,
|
||||
@SerializedName("versionInfo")
|
||||
val versionInfo: Version? = null
|
||||
) : Validation {
|
||||
override fun validate() {
|
||||
check(install != null, { "InstallProfile install cannot be null" })
|
||||
check(versionInfo != null, { "InstallProfile versionInfo cannot be null" })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.*
|
||||
|
||||
object ForgeVersionList : VersionList<Unit>() {
|
||||
@JvmField
|
||||
val FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json"
|
||||
|
||||
override fun refreshAsync(downloadProvider: DownloadProvider): Task {
|
||||
return RefreshTask(downloadProvider)
|
||||
}
|
||||
|
||||
private class RefreshTask(val downloadProvider: DownloadProvider): Task() {
|
||||
val task = GetTask(downloadProvider.injectURL(FORGE_LIST).toURL())
|
||||
override val dependents: Collection<Task> = listOf(task)
|
||||
override fun execute() {
|
||||
val root = GSON.fromJson<ForgeVersionRoot>(task.result!!) ?: return
|
||||
versions.clear()
|
||||
|
||||
for ((x, versions) in root.mcversion!!.entries) {
|
||||
val gameVersion = x.asVersion() ?: continue
|
||||
for (v in versions) {
|
||||
val version = root.number!!.get(v) ?: continue
|
||||
var jar: String? = null
|
||||
for (file in version.files!!)
|
||||
if (file.getOrNull(1) == "installer") {
|
||||
val classifier = "${version.mcversion}-${version.version}" + (
|
||||
if (isNotBlank(version.branch))
|
||||
"-${version.branch}"
|
||||
else
|
||||
""
|
||||
)
|
||||
val fileName = "${root.artifact}-$classifier-${file[1]}.${file[0]}"
|
||||
jar = downloadProvider.injectURL("${root.webpath}$classifier/$fileName")
|
||||
}
|
||||
|
||||
if (jar == null) continue
|
||||
val remoteVersion = RemoteVersion<Unit>(
|
||||
gameVersion = version.mcversion!!,
|
||||
selfVersion = version.version!!,
|
||||
url = jar,
|
||||
tag = Unit
|
||||
)
|
||||
ForgeVersionList.versions.put(gameVersion, remoteVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
|
||||
|
||||
abstract class GameBuilder {
|
||||
var name: String = ""
|
||||
protected var gameVersion: String = ""
|
||||
protected var toolVersions = HashMap<String, String>()
|
||||
|
||||
fun name(name: String): GameBuilder {
|
||||
this.name = name
|
||||
return this
|
||||
}
|
||||
|
||||
fun gameVersion(version: String): GameBuilder {
|
||||
gameVersion = version
|
||||
return this
|
||||
}
|
||||
|
||||
fun version(id: String, version: String): GameBuilder {
|
||||
toolVersions[id] = version
|
||||
return this
|
||||
}
|
||||
|
||||
abstract fun buildAsync(): Task
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.game.AssetIndex
|
||||
import org.jackhuang.hmcl.game.AssetObject
|
||||
import org.jackhuang.hmcl.game.DownloadType
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
|
||||
|
||||
/**
|
||||
* This task is to download game libraries.
|
||||
* This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task)
|
||||
* @param resolvedVersion the <b>resolved</b> version
|
||||
*/
|
||||
class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override fun execute() {
|
||||
for (library in resolvedVersion.libraries)
|
||||
if (library.appliesToCurrentEnvironment) {
|
||||
val file = dependencyManager.repository.getLibraryFile(resolvedVersion, library)
|
||||
if (!file.exists())
|
||||
dependencies += FileDownloadTask(dependencyManager.downloadProvider.injectURL(library.download.url).toURL(), file, library.download.sha1, proxy = dependencyManager.proxy)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override fun execute() {
|
||||
val logging = version.logging?.get(DownloadType.CLIENT) ?: return
|
||||
val file = dependencyManager.repository.getLoggingObject(version.actualAssetIndex.id, logging)
|
||||
if (!file.exists())
|
||||
dependencies += FileDownloadTask(logging.file.url.toURL(), file, proxy = dependencyManager.proxy)
|
||||
}
|
||||
}
|
||||
|
||||
class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override fun execute() {
|
||||
val assetIndexInfo = version.actualAssetIndex
|
||||
val assetDir = dependencyManager.repository.getAssetDirectory(assetIndexInfo.id)
|
||||
if (!assetDir.makeDirectory())
|
||||
throw IOException("Cannot create directory: $assetDir")
|
||||
|
||||
val assetIndexFile = dependencyManager.repository.getIndexFile(assetIndexInfo.id)
|
||||
dependencies += FileDownloadTask(dependencyManager.downloadProvider.injectURL(assetIndexInfo.url).toURL(), assetIndexFile, proxy = dependencyManager.proxy)
|
||||
}
|
||||
}
|
||||
|
||||
class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : TaskResult<Collection<Pair<File, AssetObject>>>() {
|
||||
private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version)
|
||||
private val assetIndexInfo = version.actualAssetIndex
|
||||
private val assetIndexFile = dependencyManager.repository.getIndexFile(assetIndexInfo.id)
|
||||
override val dependents: MutableCollection<Task> = LinkedList()
|
||||
|
||||
init {
|
||||
if (!assetIndexFile.exists())
|
||||
dependents += assetIndexTask
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val index = GSON.fromJson<AssetIndex>(assetIndexFile.readText())
|
||||
val res = LinkedList<Pair<File, AssetObject>>()
|
||||
var progress = 0
|
||||
index?.objects?.entries?.forEach { (_, assetObject) ->
|
||||
res += Pair(dependencyManager.repository.getAssetObject(assetIndexInfo.id, assetObject), assetObject)
|
||||
updateProgress(++progress, index.objects.size)
|
||||
}
|
||||
result = res
|
||||
}
|
||||
}
|
||||
|
||||
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
|
||||
override val dependents: Collection<Task> = listOf(refreshTask)
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override fun execute() {
|
||||
val size = refreshTask.result?.size ?: 0
|
||||
refreshTask.result?.forEach single@{ (file, assetObject) ->
|
||||
val url = dependencyManager.downloadProvider.assetBaseURL + assetObject.location
|
||||
if (!file.absoluteFile.parentFile.makeDirectory()) {
|
||||
LOG.severe("Unable to create new file $file, because parent directory cannot be created")
|
||||
return@single
|
||||
}
|
||||
if (file.isDirectory)
|
||||
return@single
|
||||
|
||||
var flag = true
|
||||
var downloaded = 0
|
||||
try {
|
||||
// check the checksum of file to ensure that the file is not need to re-download.
|
||||
if (file.exists()) {
|
||||
val sha1 = DigestUtils.sha1Hex(file.readBytes())
|
||||
if (assetObject.hash == sha1) {
|
||||
++downloaded
|
||||
LOG.finest("File $file has been downloaded successfully, skipped downloading")
|
||||
updateProgress(downloaded, size)
|
||||
return@single
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
LOG.log(Level.WARNING, "Unable to get hash code of file $file", e)
|
||||
flag = !file.exists()
|
||||
}
|
||||
if (flag)
|
||||
dependencies += FileDownloadTask(url.toURL(), file, assetObject.hash, proxy = dependencyManager.proxy).apply {
|
||||
title = assetObject.hash
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VersionJSONSaveTask(private val dependencyManager: DefaultDependencyManager, private val version: Version): Task() {
|
||||
override fun execute() {
|
||||
val json = dependencyManager.repository.getVersionJson(version.id).absoluteFile
|
||||
if (!json.makeFile())
|
||||
throw IOException("Cannot create file $json")
|
||||
json.writeText(GSON.toJson(version))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.ReleaseType
|
||||
import org.jackhuang.hmcl.util.DEFAULT_LIBRARY_URL
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import org.jackhuang.hmcl.util.Validation
|
||||
import java.util.*
|
||||
|
||||
@Immutable
|
||||
internal class GameRemoteLatestVersions(
|
||||
@SerializedName("snapshot")
|
||||
val snapshot: String,
|
||||
@SerializedName("release")
|
||||
val release: String
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal class GameRemoteVersion(
|
||||
@SerializedName("id")
|
||||
val gameVersion: String = "",
|
||||
@SerializedName("time")
|
||||
val time: Date = Date(),
|
||||
@SerializedName("releaseTime")
|
||||
val releaseTime: Date = Date(),
|
||||
@SerializedName("type")
|
||||
val type: ReleaseType = ReleaseType.UNKNOWN,
|
||||
@SerializedName("url")
|
||||
val url: String = "$DEFAULT_LIBRARY_URL$gameVersion/$gameVersion.json"
|
||||
) : Validation {
|
||||
override fun validate() {
|
||||
if (gameVersion.isBlank())
|
||||
throw IllegalArgumentException("GameRemoteVersion id cannot be blank")
|
||||
if (url.isBlank())
|
||||
throw IllegalArgumentException("GameRemoteVersion url cannot be blank")
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal class GameRemoteVersions(
|
||||
@SerializedName("versions")
|
||||
val versions: List<GameRemoteVersion> = emptyList(),
|
||||
|
||||
@SerializedName("latest")
|
||||
val latest: GameRemoteLatestVersions? = null
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class GameRemoteVersionTag (
|
||||
val type: ReleaseType,
|
||||
val time: Date
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.asVersion
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
|
||||
|
||||
object GameVersionList : VersionList<GameRemoteVersionTag>() {
|
||||
|
||||
override fun refreshAsync(downloadProvider: DownloadProvider): Task {
|
||||
return RefreshTask(downloadProvider)
|
||||
}
|
||||
|
||||
private class RefreshTask(provider: DownloadProvider) : Task() {
|
||||
val task = GetTask(provider.versionListURL.toURL())
|
||||
override val dependents: Collection<Task> = listOf(task)
|
||||
override fun execute() {
|
||||
versionMap.clear()
|
||||
versions.clear()
|
||||
|
||||
val root = GSON.fromJson<GameRemoteVersions>(task.result!!) ?: return
|
||||
for (remoteVersion in root.versions) {
|
||||
val gg = remoteVersion.gameVersion.asVersion() ?: continue
|
||||
val x = RemoteVersion(
|
||||
gameVersion = remoteVersion.gameVersion,
|
||||
selfVersion = remoteVersion.gameVersion,
|
||||
url = remoteVersion.url,
|
||||
tag = GameRemoteVersionTag(
|
||||
type = remoteVersion.type,
|
||||
time = remoteVersion.releaseTime
|
||||
)
|
||||
)
|
||||
versions.add(x)
|
||||
versionMap[gg] = listOf(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.task.then
|
||||
import org.jackhuang.hmcl.util.merge
|
||||
|
||||
/**
|
||||
* LiteLoader must be installed after Forge.
|
||||
*/
|
||||
class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
|
||||
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
|
||||
init {
|
||||
if (version.jar == null)
|
||||
throw IllegalArgumentException()
|
||||
if (!liteLoaderVersionList.loaded)
|
||||
dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider) then {
|
||||
remote = liteLoaderVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version ${version.jar}, $remoteVersion not found")
|
||||
null
|
||||
}
|
||||
else {
|
||||
remote = liteLoaderVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version ${version.jar}, $remoteVersion not found")
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val library = Library(
|
||||
groupId = "com.mumfrey",
|
||||
artifactId = "liteloader",
|
||||
version = remote.selfVersion,
|
||||
url = "http://dl.liteloader.com/versions/",
|
||||
downloads = LibrariesDownloadInfo(
|
||||
artifact = LibraryDownloadInfo(
|
||||
url = remote.url
|
||||
)
|
||||
)
|
||||
)
|
||||
val tempVersion = version.copy(libraries = merge(remote.tag.libraries, listOf(library)))
|
||||
result = version.copy(
|
||||
mainClass = "net.minecraft.launchwrapper.Launch",
|
||||
minecraftArguments = version.minecraftArguments + " --tweakClass " + remote.tag.tweakClass,
|
||||
libraries = merge(tempVersion.libraries, version.libraries)
|
||||
)
|
||||
dependencies += GameLibrariesTask(dependencyManager, tempVersion)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
|
||||
@Immutable
|
||||
internal data class LiteLoaderVersionsMeta (
|
||||
@SerializedName("description")
|
||||
val description: String = "",
|
||||
@SerializedName("authors")
|
||||
val authors: String = "",
|
||||
@SerializedName("url")
|
||||
val url: String = ""
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal data class LiteLoaderRepository (
|
||||
@SerializedName("stream")
|
||||
val stream: String = "",
|
||||
@SerializedName("type")
|
||||
val type: String = "",
|
||||
@SerializedName("url")
|
||||
val url: String = "",
|
||||
@SerializedName("classifier")
|
||||
val classifier: String = ""
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal class LiteLoaderVersion (
|
||||
@SerializedName("tweakClass")
|
||||
val tweakClass: String = "",
|
||||
@SerializedName("file")
|
||||
val file: String = "",
|
||||
@SerializedName("version")
|
||||
val version: String = "",
|
||||
@SerializedName("md5")
|
||||
val md5: String = "",
|
||||
@SerializedName("timestamp")
|
||||
val timestamp: String = "",
|
||||
@SerializedName("lastSuccessfulBuild")
|
||||
val lastSuccessfulBuild: Int = 0,
|
||||
@SerializedName("libraries")
|
||||
val libraries: Collection<Library> = emptyList()
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal class LiteLoaderBranch (
|
||||
@SerializedName("libraries")
|
||||
val libraries: Collection<Library> = emptyList(),
|
||||
@SerializedName("com.mumfrey:liteloader")
|
||||
val liteLoader: Map<String, LiteLoaderVersion> = emptyMap()
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal class LiteLoaderGameVersions (
|
||||
@SerializedName("repo")
|
||||
val repo: LiteLoaderRepository? = null,
|
||||
@SerializedName("artefacts")
|
||||
val artifacts: LiteLoaderBranch? = null,
|
||||
@SerializedName("snapshots")
|
||||
val snapshots: LiteLoaderBranch? = null
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal class LiteLoaderVersionsRoot (
|
||||
@SerializedName("versions")
|
||||
val versions: Map<String, LiteLoaderGameVersions> = emptyMap(),
|
||||
@SerializedName("meta")
|
||||
val meta: LiteLoaderVersionsMeta? = null
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class LiteLoaderRemoteVersionTag (
|
||||
val tweakClass: String,
|
||||
val libraries: Collection<Library>
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.asVersion
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
|
||||
object LiteLoaderVersionList : VersionList<LiteLoaderRemoteVersionTag>() {
|
||||
@JvmField
|
||||
val LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json"
|
||||
|
||||
override fun refreshAsync(downloadProvider: DownloadProvider): Task {
|
||||
return RefreshTask(downloadProvider)
|
||||
}
|
||||
|
||||
internal class RefreshTask(val downloadProvider: DownloadProvider) : Task() {
|
||||
val task = GetTask(downloadProvider.injectURL(LITELOADER_LIST).toURL())
|
||||
override val dependents: Collection<Task> = listOf(task)
|
||||
override fun execute() {
|
||||
val root = GSON.fromJson<LiteLoaderVersionsRoot>(task.result!!) ?: return
|
||||
versions.clear()
|
||||
|
||||
for ((gameVersion, liteLoader) in root.versions.entries) {
|
||||
val gg = gameVersion.asVersion() ?: continue
|
||||
doBranch(gg, gameVersion, liteLoader.repo, liteLoader.artifacts, false)
|
||||
doBranch(gg, gameVersion, liteLoader.repo, liteLoader.snapshots, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doBranch(key: String, gameVersion: String, repository: LiteLoaderRepository?, branch: LiteLoaderBranch?, snapshot: Boolean) {
|
||||
if (branch == null || repository == null)
|
||||
return
|
||||
for ((branchName, v) in branch.liteLoader.entries) {
|
||||
if ("latest" == branchName)
|
||||
continue
|
||||
val iv = RemoteVersion<LiteLoaderRemoteVersionTag>(
|
||||
selfVersion = v.version.replace("SNAPSHOT", "SNAPSHOT-" + v.lastSuccessfulBuild),
|
||||
gameVersion = gameVersion,
|
||||
url = if (snapshot)
|
||||
"http://jenkins.liteloader.com/view/$gameVersion/job/LiteLoader $gameVersion/lastSuccessfulBuild/artifact/build/libs/liteloader-${v.version}-release.jar"
|
||||
else
|
||||
downloadProvider.injectURL(repository.url + "com/mumfrey/liteloader/" + gameVersion + "/" + v.file),
|
||||
tag = LiteLoaderRemoteVersionTag(tweakClass = v.tweakClass, libraries = v.libraries)
|
||||
)
|
||||
|
||||
versions.put(key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
|
||||
import java.util.*
|
||||
|
||||
object MojangDownloadProvider : DownloadProvider() {
|
||||
override val libraryBaseURL: String = "https://libraries.minecraft.net/"
|
||||
override val versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/"
|
||||
override val versionListURL: String = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
|
||||
override val assetIndexBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/indexes/"
|
||||
override val assetBaseURL: String = "http://resources.download.minecraft.net/"
|
||||
|
||||
override fun getVersionListById(id: String): VersionList<*> {
|
||||
return when(id) {
|
||||
"game" -> GameVersionList
|
||||
"forge" -> ForgeVersionList
|
||||
"liteloader" -> LiteLoaderVersionList
|
||||
"optifine" -> OptiFineVersionList
|
||||
else -> throw IllegalArgumentException("Unrecognized version list id: $id")
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectURL(baseURL: String): String {
|
||||
/**if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven/");
|
||||
else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
|
||||
if (Locale.getDefault() == Locale.CHINA)
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
|
||||
else
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://repo1.maven.org/maven2");
|
||||
else
|
||||
return baseURL; */
|
||||
if (baseURL.endsWith("net/minecraftforge/forge/json"))
|
||||
return baseURL
|
||||
else if (Locale.getDefault() == Locale.CHINA)
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
|
||||
else
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.asVersion
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import org.jackhuang.hmcl.util.typeOf
|
||||
import java.util.TreeSet
|
||||
|
||||
object OptiFineBMCLVersionList : VersionList<Unit>() {
|
||||
override fun refreshAsync(downloadProvider: DownloadProvider): Task {
|
||||
return RefreshTask(downloadProvider)
|
||||
}
|
||||
|
||||
private class RefreshTask(val downloadProvider: DownloadProvider): Task() {
|
||||
val task = GetTask("http://bmclapi.bangbang93.com/optifine/versionlist".toURL())
|
||||
override val dependents: Collection<Task> = listOf(task)
|
||||
override fun execute() {
|
||||
versionMap.clear()
|
||||
versions.clear()
|
||||
|
||||
val duplicates = mutableSetOf<String>()
|
||||
val root = GSON.fromJson<List<OptiFineVersion>>(task.result!!, typeOf<List<OptiFineVersion>>())
|
||||
for (element in root) {
|
||||
val version = element.type ?: continue
|
||||
val mirror = "http://bmclapi2.bangbang93.com/optifine/${element.gameVersion}/${element.type}/${element.patch}"
|
||||
if (duplicates.contains(mirror))
|
||||
continue
|
||||
else
|
||||
duplicates += mirror
|
||||
|
||||
val gameVersion = element.gameVersion?.asVersion() ?: continue
|
||||
val remoteVersion = RemoteVersion<Unit>(
|
||||
gameVersion = gameVersion,
|
||||
selfVersion = version,
|
||||
url = mirror,
|
||||
tag = Unit
|
||||
)
|
||||
|
||||
val set = versionMap.getOrPut(gameVersion, { TreeSet<RemoteVersion<Unit>>() }) as MutableCollection<RemoteVersion<Unit>>
|
||||
set.add(remoteVersion)
|
||||
versions.add(remoteVersion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
|
||||
object Opt
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask
|
||||
import org.jackhuang.hmcl.game.LibrariesDownloadInfo
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.task.then
|
||||
import org.jackhuang.hmcl.util.merge
|
||||
|
||||
class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val optiFineVersionList = dependencyManager.getVersionList("optifine")
|
||||
lateinit var remote: RemoteVersion<*>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
|
||||
init {
|
||||
if (version.jar == null)
|
||||
throw IllegalArgumentException()
|
||||
if (!optiFineVersionList.loaded)
|
||||
dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then {
|
||||
remote = optiFineVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $remoteVersion not found")
|
||||
null
|
||||
}
|
||||
else {
|
||||
remote = optiFineVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $remoteVersion not found")
|
||||
}
|
||||
}
|
||||
override fun execute() {
|
||||
val library = Library(
|
||||
groupId = "net.optifine",
|
||||
artifactId = "optifine",
|
||||
version = remoteVersion,
|
||||
lateload = true,
|
||||
downloads = LibrariesDownloadInfo(
|
||||
artifact = LibraryDownloadInfo(
|
||||
path = "net/optifine/optifine/$remoteVersion/optifine-$remoteVersion.jar",
|
||||
url = remote.url
|
||||
)
|
||||
))
|
||||
val libraries = mutableListOf(library)
|
||||
var arg = version.minecraftArguments!!
|
||||
if (!arg.contains("FMLTweaker"))
|
||||
arg += " --tweakClass optifine.OptiFineTweaker"
|
||||
var mainClass = version.mainClass
|
||||
if (mainClass == null || !mainClass.startsWith("net.minecraft.launchwrapper.")) {
|
||||
mainClass = "net.minecraft.launchwrapper.Launch"
|
||||
libraries.add(0, Library(
|
||||
groupId = "net.minecraft",
|
||||
artifactId = "launchwrapper",
|
||||
version = "1.12"
|
||||
))
|
||||
}
|
||||
result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg)
|
||||
dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OptiFineVersion (
|
||||
@SerializedName("dl")
|
||||
val downloadLink: String? = null,
|
||||
@SerializedName("ver")
|
||||
val version: String? = null,
|
||||
@SerializedName("date")
|
||||
val date: String? = null,
|
||||
@SerializedName("type")
|
||||
val type: String? = null,
|
||||
@SerializedName("patch")
|
||||
val patch: String? = null,
|
||||
@SerializedName("mirror")
|
||||
val mirror: String? = null,
|
||||
@SerializedName("mcversion")
|
||||
val gameVersion: String? = null
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import org.w3c.dom.Element
|
||||
import java.io.ByteArrayInputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object OptiFineVersionList : VersionList<Unit>() {
|
||||
private val pattern = Pattern.compile("OptiFine (.*?) ")
|
||||
|
||||
override fun refreshAsync(downloadProvider: DownloadProvider): Task {
|
||||
return RefreshTask(downloadProvider)
|
||||
}
|
||||
|
||||
private class RefreshTask(val downloadProvider: DownloadProvider) : Task() {
|
||||
val task = GetTask("http://optifine.net/downloads".toURL())
|
||||
override val dependents: Collection<Task> = listOf(task)
|
||||
override fun execute() {
|
||||
versions.clear()
|
||||
|
||||
val html = task.result!!.replace(" ", " ").replace(">", ">").replace("<", "<").replace("<br>", "<br />")
|
||||
|
||||
val factory = DocumentBuilderFactory.newInstance()
|
||||
val db = factory.newDocumentBuilder()
|
||||
val doc = db.parse(ByteArrayInputStream(html.toByteArray()))
|
||||
val r = doc.documentElement
|
||||
val tables = r.getElementsByTagName("table")
|
||||
for (i in 0..tables.length - 1) {
|
||||
val e = tables.item(i) as Element
|
||||
if ("downloadTable" == e.getAttribute("class")) {
|
||||
val tr = e.getElementsByTagName("tr")
|
||||
for (k in 0..tr.length - 1) {
|
||||
val downloadLine = (tr.item(k) as Element).getElementsByTagName("td")
|
||||
var url: String? = null
|
||||
var version: String? = null
|
||||
for (j in 0..downloadLine.length - 1) {
|
||||
val td = downloadLine.item(j) as Element
|
||||
if (td.getAttribute("class")?.startsWith("downloadLineMirror") ?: false)
|
||||
url = (td.getElementsByTagName("a").item(0) as Element).getAttribute("href")
|
||||
if (td.getAttribute("class")?.startsWith("downloadLineFile") ?: false)
|
||||
version = td.textContent
|
||||
}
|
||||
val matcher = pattern.matcher(version)
|
||||
var gameVersion: String? = null
|
||||
while (matcher.find())
|
||||
gameVersion = matcher.group(1)
|
||||
if (gameVersion == null || version == null || url == null) continue
|
||||
val remoteVersion = RemoteVersion(
|
||||
gameVersion = gameVersion,
|
||||
selfVersion = version,
|
||||
url = url,
|
||||
tag = Unit
|
||||
)
|
||||
versions.put(gameVersion, remoteVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.util.VersionNumber
|
||||
import java.util.*
|
||||
import kotlin.Comparator
|
||||
|
||||
data class RemoteVersion<T> (
|
||||
val gameVersion: String,
|
||||
val selfVersion: String,
|
||||
/**
|
||||
* The file of remote version, may be an installer or an universal jar.
|
||||
*/
|
||||
val url: String,
|
||||
val tag: T
|
||||
): Comparable<RemoteVersion<T>> {
|
||||
override fun hashCode(): Int {
|
||||
return selfVersion.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is RemoteVersion<*> && Objects.equals(this.selfVersion, other.selfVersion)
|
||||
}
|
||||
|
||||
override fun compareTo(other: RemoteVersion<T>): Int {
|
||||
return -selfVersion.compareTo(other.selfVersion)
|
||||
}
|
||||
|
||||
companion object RemoteVersionComparator: Comparator<RemoteVersion<*>> {
|
||||
override fun compare(o1: RemoteVersion<*>, o2: RemoteVersion<*>): Int {
|
||||
return -VersionNumber.asVersion(o1.selfVersion).compareTo(VersionNumber.asVersion(o2.selfVersion))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.download
|
||||
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
abstract class VersionList<T> {
|
||||
@JvmField
|
||||
protected val versions = SimpleMultimap<String, RemoteVersion<T>>(::HashMap, ::TreeSet)
|
||||
|
||||
val loaded = versions.isNotEmpty
|
||||
|
||||
abstract fun refreshAsync(downloadProvider: DownloadProvider): Task
|
||||
|
||||
protected open fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> {
|
||||
return versions[gameVersion] ?: versions.values
|
||||
}
|
||||
|
||||
/**
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @return the collection of specific remote versions
|
||||
*/
|
||||
fun getVersions(gameVersion: String): Collection<RemoteVersion<T>> {
|
||||
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion))
|
||||
}
|
||||
|
||||
fun getVersion(gameVersion: String, remoteVersion: String): RemoteVersion<T>? {
|
||||
var result : RemoteVersion<T>? = null
|
||||
versions[gameVersion]?.forEach {
|
||||
if (it.selfVersion == remoteVersion)
|
||||
result = it
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
60
HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.kt
Normal file
60
HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.*
|
||||
|
||||
open class Event(source: Any) : EventObject(source) {
|
||||
/**
|
||||
* true if this event is canceled.
|
||||
*
|
||||
* @throws UnsupportedOperationException if trying to cancel a non-cancelable event.
|
||||
*/
|
||||
var isCanceled = false
|
||||
set(value) {
|
||||
if (!isCancelable)
|
||||
throw UnsupportedOperationException("Attempted to cancel a non-cancelable event: ${this.javaClass}")
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Retutns the value set as the result of this event
|
||||
*/
|
||||
var result = Result.DEFAULT
|
||||
set(value) {
|
||||
if (!hasResult)
|
||||
throw UnsupportedOperationException("Attempted to set result on a no result event: ${this.javaClass} of type.")
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* true if this Event this cancelable.
|
||||
*/
|
||||
open val isCancelable = false
|
||||
|
||||
/**
|
||||
* true if this event has a significant result.
|
||||
*/
|
||||
open val hasResult = false
|
||||
|
||||
enum class Result {
|
||||
DENY,
|
||||
DEFAULT,
|
||||
ALLOW
|
||||
}
|
||||
}
|
||||
38
HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.kt
Normal file
38
HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.*
|
||||
|
||||
class EventBus {
|
||||
val events = HashMap<Class<*>, EventManager<*>>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : EventObject> channel(classOfT: Class<T>): EventManager<T> {
|
||||
if (!events.containsKey(classOfT))
|
||||
events.put(classOfT, EventManager<T>())
|
||||
return events[classOfT] as EventManager<T>
|
||||
}
|
||||
|
||||
inline fun <reified T: EventObject> channel() = channel(T::class.java)
|
||||
|
||||
fun fireEvent(obj: EventObject) {
|
||||
channel(obj.javaClass).fireEvent(obj)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.*
|
||||
|
||||
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>())
|
||||
}
|
||||
|
||||
fun register(func: (T) -> Unit, priority: EventPriority = EventPriority.NORMAL) {
|
||||
if (!handlers[priority]!!.contains(func))
|
||||
handlers[priority]!!.add(func)
|
||||
}
|
||||
|
||||
fun unregister(func: (T) -> Unit) {
|
||||
EventPriority.values().forEach { handlers[it]!!.remove(func) }
|
||||
}
|
||||
|
||||
fun fireEvent(event: T) {
|
||||
for (priority in EventPriority.values())
|
||||
for (handler in handlers[priority]!!)
|
||||
handler(event)
|
||||
}
|
||||
|
||||
operator fun plusAssign(func: (T) -> Unit) = register(func)
|
||||
operator fun minusAssign(func: (T) -> Unit) = unregister(func)
|
||||
operator fun invoke(event: T) = fireEvent(event)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
enum class EventPriority {
|
||||
HIGHEST,
|
||||
HIGH,
|
||||
NORMAL,
|
||||
LOW,
|
||||
LOWEST
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
open class FailedEvent<T>(source: Any, val failedTime: Int, var newResult: T) : Event(source) {
|
||||
}
|
||||
33
HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.kt
Normal file
33
HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
import java.util.*
|
||||
|
||||
class AssetIndex(
|
||||
@SerializedName("virtual")
|
||||
val virtual: Boolean = false,
|
||||
objects: Map<String, AssetObject> = emptyMap()
|
||||
) {
|
||||
val objects: Map<String, AssetObject>
|
||||
get() = Collections.unmodifiableMap(objectsImpl)
|
||||
|
||||
@SerializedName("objects")
|
||||
private val objectsImpl: MutableMap<String, AssetObject> = HashMap(objects)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 jdk.nashorn.internal.ir.annotations.Immutable
|
||||
import java.net.URL
|
||||
|
||||
@Immutable
|
||||
class AssetIndexInfo(
|
||||
url: String = "",
|
||||
sha1: String? = null,
|
||||
size: Int = 0,
|
||||
id: String = "",
|
||||
val totalSize: Long = 0
|
||||
) : IdDownloadInfo(url, sha1, size, id)
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.Validation
|
||||
|
||||
data class AssetObject(
|
||||
val hash: String = "",
|
||||
val size: Long = 0
|
||||
): Validation {
|
||||
val location: String
|
||||
get() = hash.substring(0, 2) + "/" + hash
|
||||
|
||||
override fun validate() {
|
||||
check(hash.isNotBlank(), { "AssetObject hash cannot be blank." })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* What's circle dependency?
|
||||
* When C inherits from B, and B inherits from something else, and finally inherits from C again.
|
||||
*/
|
||||
class CircleDependencyException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.Immutable
|
||||
import java.io.File
|
||||
|
||||
@Immutable
|
||||
class ClassicVersion : Version(
|
||||
mainClass = "net.minecraft.client.Minecraft",
|
||||
id = "Classic",
|
||||
type = ReleaseType.UNKNOWN,
|
||||
minecraftArguments = "\${auth_player_name} \${auth_session} --workDir \${game_directory}",
|
||||
libraries = listOf(
|
||||
ClassicLibrary("lwjgl"),
|
||||
ClassicLibrary("jinput"),
|
||||
ClassicLibrary("lwjgl_util")
|
||||
)
|
||||
) {
|
||||
@Immutable
|
||||
private class ClassicLibrary(name: String) :
|
||||
Library(groupId = "", artifactId = "", version = "",
|
||||
downloads = LibrariesDownloadInfo(
|
||||
artifact = LibraryDownloadInfo(path = "bin/$name.jar")
|
||||
)
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun hasClassicVersion(baseDirectory: File): Boolean {
|
||||
val file = File(baseDirectory, "bin")
|
||||
if (!file.exists()) return false
|
||||
if (!File(file, "lwjgl.jar").exists()) return false
|
||||
if (!File(file, "jinput.jar").exists()) return false
|
||||
if (!File(file, "lwjgl_util.jar").exists()) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import org.jackhuang.hmcl.util.OS
|
||||
import org.jackhuang.hmcl.util.ignoreThrowable
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Immutable
|
||||
data class CompatibilityRule(
|
||||
val action: Action = CompatibilityRule.Action.ALLOW,
|
||||
val os: OSRestriction? = null
|
||||
) {
|
||||
|
||||
val appliedAction: Action? get() = if (os != null && !os.allow()) null else action
|
||||
|
||||
companion object {
|
||||
fun appliesToCurrentEnvironment(rules: Collection<CompatibilityRule>?): Boolean {
|
||||
if (rules == null)
|
||||
return true
|
||||
var action = CompatibilityRule.Action.DISALLOW
|
||||
for (rule in rules) {
|
||||
val thisAction = rule.appliedAction
|
||||
if (thisAction != null) action = thisAction
|
||||
}
|
||||
return action == CompatibilityRule.Action.ALLOW
|
||||
}
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
@SerializedName("allow")
|
||||
ALLOW,
|
||||
@SerializedName("disallow")
|
||||
DISALLOW
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class OSRestriction(
|
||||
val name: OS = OS.UNKNOWN,
|
||||
val version: String? = null,
|
||||
val arch: String? = null
|
||||
) {
|
||||
fun allow(): Boolean {
|
||||
if (name != OS.UNKNOWN && name != OS.CURRENT_OS)
|
||||
return false
|
||||
if (version != null) {
|
||||
ignoreThrowable {
|
||||
if (!Pattern.compile(version).matcher(OS.SYSTEM_VERSION).matches())
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (arch != null)
|
||||
ignoreThrowable {
|
||||
if (!Pattern.compile(arch).matcher(OS.SYSTEM_ARCH).matches())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonSyntaxException
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
import org.jackhuang.hmcl.util.listFilesByExtension
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
|
||||
open class DefaultGameRepository(val baseDirectory: File): GameRepository {
|
||||
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
|
||||
|
||||
override fun hasVersion(id: String) = versions.containsKey(id)
|
||||
override fun getVersion(id: String): Version {
|
||||
return versions[id] ?: throw VersionNotFoundException("Version '$id' does not exist.")
|
||||
}
|
||||
override fun getVersionCount() = versions.size
|
||||
override fun getVersions() = versions.values
|
||||
override fun getLibraryFile(id: Version, lib: Library) = File(baseDirectory, "libraries/${lib.path}")
|
||||
override fun getRunDirectory(id: String) = baseDirectory
|
||||
override fun getVersionJar(version: Version): File {
|
||||
val v = version.resolve(this)
|
||||
val id = v.id
|
||||
return getVersionRoot(id).resolve("$id.jar")
|
||||
}
|
||||
override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives")
|
||||
open fun getVersionRoot(id: String) = File(baseDirectory, "versions/$id")
|
||||
open fun getVersionJson(id: String) = File(getVersionRoot(id), "$id.json")
|
||||
open fun readVersionJson(id: String): Version? = readVersionJson(getVersionJson(id))
|
||||
@Throws(IOException::class, JsonSyntaxException::class, VersionNotFoundException::class)
|
||||
open fun readVersionJson(file: File): Version? = GSON.fromJson<Version>(file.readText())
|
||||
override fun renameVersion(from: String, to: String): Boolean {
|
||||
try {
|
||||
val fromVersion = getVersion(from)
|
||||
val fromDir = getVersionRoot(from)
|
||||
val toDir = getVersionRoot(to)
|
||||
if (!fromDir.renameTo(toDir))
|
||||
return false
|
||||
val toJson = File(toDir, "$to.json")
|
||||
val toJar = File(toDir, "$to.jar")
|
||||
if (!File(toDir, "$from.json").renameTo(toJson) ||
|
||||
!File(toDir, "$from.jar").renameTo(toJar)) {
|
||||
toDir.renameTo(fromDir) // simple recovery
|
||||
return false
|
||||
}
|
||||
toJson.writeText(GSON.toJson(fromVersion.copy(id = to)))
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
return false
|
||||
} catch (e2: JsonSyntaxException) {
|
||||
return false
|
||||
} catch (e: VersionNotFoundException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
open fun removeVersionFromDisk(name: String): Boolean {
|
||||
val file = getVersionRoot(name)
|
||||
if (!file.exists())
|
||||
return true
|
||||
versions.remove(name)
|
||||
return file.deleteRecursively()
|
||||
}
|
||||
|
||||
|
||||
@Synchronized
|
||||
override fun refreshVersions() {
|
||||
versions.clear()
|
||||
|
||||
if (ClassicVersion.hasClassicVersion(baseDirectory)) {
|
||||
val version = ClassicVersion()
|
||||
versions[version.id] = version
|
||||
}
|
||||
|
||||
baseDirectory.resolve("versions").listFiles()?.filter { it.isDirectory }?.forEach tryVersion@{ dir ->
|
||||
val id = dir.name
|
||||
val json = dir.resolve("$id.json")
|
||||
|
||||
// If user renamed the json file by mistake or created the json file in a wrong name,
|
||||
// we will find the only json and rename it to correct name.
|
||||
if (!json.exists()) {
|
||||
val jsons = dir.listFilesByExtension("json")
|
||||
if (jsons.size == 1)
|
||||
jsons.single().renameTo(json)
|
||||
}
|
||||
|
||||
var version: Version
|
||||
try {
|
||||
version = readVersionJson(json)!!
|
||||
} catch(e: Exception) { // JsonSyntaxException or IOException or NullPointerException(!!)
|
||||
// TODO: auto making up for the missing json
|
||||
// TODO: and even asking for removing the redundant version folder.
|
||||
return@tryVersion
|
||||
}
|
||||
|
||||
if (id != version.id) {
|
||||
version = version.copy(id = id)
|
||||
try {
|
||||
json.writeText(GSON.toJson(version))
|
||||
} catch(e: Exception) {
|
||||
LOG.warning("Ignoring version $id because wrong id ${version.id} is set and cannot correct it.")
|
||||
return@tryVersion
|
||||
}
|
||||
}
|
||||
|
||||
versions[id] = version
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAssetIndex(assetId: String): AssetIndex {
|
||||
return GSON.fromJson(getIndexFile(assetId).readText())
|
||||
}
|
||||
|
||||
override fun getActualAssetDirectory(assetId: String): File {
|
||||
try {
|
||||
return reconstructAssets(assetId)
|
||||
} catch (e: IOException) {
|
||||
LOG.log(Level.SEVERE, "Unable to reconstruct asset directory", e)
|
||||
return getAssetDirectory(assetId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAssetDirectory(assetId: String): File =
|
||||
baseDirectory.resolve("assets")
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getAssetObject(assetId: String, name: String): File {
|
||||
try {
|
||||
return getAssetObject(assetId, getAssetIndex(assetId).objects["name"]!!)
|
||||
} catch (e: Exception) {
|
||||
throw IOException("Asset index file malformed", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAssetObject(assetId: String, obj: AssetObject): File =
|
||||
getAssetObject(getAssetDirectory(assetId), obj)
|
||||
|
||||
open fun getAssetObject(assetDir: File, obj: AssetObject): File {
|
||||
return assetDir.resolve("objects/${obj.location}")
|
||||
}
|
||||
|
||||
open fun getIndexFile(assetId: String): File =
|
||||
getAssetDirectory(assetId).resolve("indexes/$assetId.json")
|
||||
|
||||
override fun getLoggingObject(assetId: String, loggingInfo: LoggingInfo): File =
|
||||
getAssetDirectory(assetId).resolve("log_configs/${loggingInfo.file.id}")
|
||||
|
||||
@Throws(IOException::class, JsonSyntaxException::class)
|
||||
protected open fun reconstructAssets(assetId: String): File {
|
||||
val assetsDir = getAssetDirectory(assetId)
|
||||
val assetVersion = assetId
|
||||
val indexFile: File = getIndexFile(assetVersion)
|
||||
val virtualRoot = assetsDir.resolve("virtual").resolve(assetVersion)
|
||||
|
||||
if (!indexFile.isFile) {
|
||||
return assetsDir
|
||||
}
|
||||
|
||||
val assetIndexContent = indexFile.readText()
|
||||
val index = GSON.fromJson(assetIndexContent, AssetIndex::class.java) ?: return assetsDir
|
||||
|
||||
if (index.virtual) {
|
||||
var cnt = 0
|
||||
LOG.info("Reconstructing virtual assets folder at " + virtualRoot)
|
||||
val tot = index.objects.entries.size
|
||||
for ((location, assetObject) in index.objects.entries) {
|
||||
val target = File(virtualRoot, location)
|
||||
val original = getAssetObject(assetsDir, assetObject)
|
||||
if (original.exists()) {
|
||||
cnt++
|
||||
if (!target.isFile)
|
||||
original.copyTo(target)
|
||||
}
|
||||
}
|
||||
// If the scale new format existent file is lower then 0.1, use the old format.
|
||||
if (cnt * 10 < tot)
|
||||
return assetsDir
|
||||
else
|
||||
return virtualRoot
|
||||
}
|
||||
|
||||
return assetsDir
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import org.jackhuang.hmcl.util.Validation
|
||||
|
||||
@Immutable
|
||||
open class DownloadInfo(
|
||||
@SerializedName("url")
|
||||
val url: String = "",
|
||||
@SerializedName("sha1")
|
||||
val sha1: String? = null,
|
||||
@SerializedName("size")
|
||||
val size: Int = 0
|
||||
): Validation {
|
||||
override fun validate() {
|
||||
if (url.isBlank())
|
||||
throw JsonSyntaxException("DownloadInfo url can not be null")
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
open class IdDownloadInfo(
|
||||
url: String = "",
|
||||
sha1: String? = null,
|
||||
size: Int = 0,
|
||||
@SerializedName("id")
|
||||
val id: String = ""
|
||||
): DownloadInfo(url, sha1, size) {
|
||||
override fun validate() {
|
||||
super.validate()
|
||||
|
||||
if (id.isBlank())
|
||||
throw JsonSyntaxException("IdDownloadInfo id can not be null")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class DownloadType {
|
||||
@SerializedName("client")
|
||||
CLIENT,
|
||||
@SerializedName("server")
|
||||
SERVER,
|
||||
@SerializedName("windows-server")
|
||||
WINDOWS_SERVER
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
import java.util.*
|
||||
|
||||
class ExtractRules(exclude: List<String> = emptyList()) {
|
||||
val exclude: List<String> get() = Collections.unmodifiableList(excludeImpl)
|
||||
|
||||
@SerializedName("exclude")
|
||||
private val excludeImpl: MutableList<String> = LinkedList(exclude)
|
||||
|
||||
fun shouldExtract(path: String): Boolean =
|
||||
exclude.filter { path.startsWith(it) }.isEmpty()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
class GameException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
160
HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.kt
Normal file
160
HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 java.io.File
|
||||
|
||||
/**
|
||||
* Supports operations on versioning.
|
||||
*
|
||||
* Note that game repository will not do any tasks that connect to Internet.
|
||||
*/
|
||||
interface GameRepository : VersionProvider {
|
||||
/**
|
||||
* Does the version of id exist?
|
||||
* @param id the id of version
|
||||
* @return true if the version exists
|
||||
*/
|
||||
override fun hasVersion(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Get the version
|
||||
* @param id the id of version
|
||||
* @return the version you want
|
||||
*/
|
||||
override fun getVersion(id: String): Version
|
||||
|
||||
/**
|
||||
* How many version are there?
|
||||
*/
|
||||
fun getVersionCount(): Int
|
||||
|
||||
/**
|
||||
* Gets the collection of versions
|
||||
* @return the collection of versions
|
||||
*/
|
||||
fun getVersions(): Collection<Version>
|
||||
|
||||
/**
|
||||
* Load version list.
|
||||
*
|
||||
* This method should be called before launching a version.
|
||||
* A time-costly operation.
|
||||
* You'd better execute this method in a new thread.
|
||||
*/
|
||||
fun refreshVersions()
|
||||
|
||||
/**
|
||||
* Gets the current running directory of the given version for game.
|
||||
* @param id the version id
|
||||
*/
|
||||
fun getRunDirectory(id: String): File
|
||||
|
||||
/**
|
||||
* Get the library file in disk.
|
||||
* This method allows versions and libraries that are not loaded by this game repository.
|
||||
*
|
||||
* @param id version id
|
||||
* @param lib the library, [Version.libraries]
|
||||
* @return the library file
|
||||
*/
|
||||
fun getLibraryFile(id: Version, lib: Library): File
|
||||
|
||||
/**
|
||||
* Get the directory that native libraries will be unzipped to.
|
||||
*
|
||||
* You'd better return a unique directory.
|
||||
* Or if it returns a temporary directory, [org.jackhuang.hmcl.launch.Launcher.makeLaunchScript] will fail.
|
||||
* If you do want to return a temporary directory, make [org.jackhuang.hmcl.launch.Launcher.makeLaunchScript] always fail([UnsupportedOperationException]) and not to use it.
|
||||
*
|
||||
* @param id version id
|
||||
* @return the native directory
|
||||
*/
|
||||
fun getNativeDirectory(id: String): File
|
||||
|
||||
/**
|
||||
* Get minecraft jar
|
||||
*
|
||||
* @param version resolvedVersion
|
||||
* @return the minecraft jar
|
||||
*/
|
||||
fun getVersionJar(version: Version): File
|
||||
|
||||
/**
|
||||
* Rename given version to new name.
|
||||
*
|
||||
* @param from The id of original version
|
||||
* @param to The new id of the version
|
||||
* @throws UnsupportedOperationException if this game repository does not support renaming a version
|
||||
* @throws java.io.IOException if I/O operation fails.
|
||||
* @return true if the operation is done successfully.
|
||||
*/
|
||||
fun renameVersion(from: String, to: String): Boolean
|
||||
|
||||
/**
|
||||
* Get actual asset directory.
|
||||
* Will reconstruct assets or do some blocking tasks if necessary.
|
||||
* You'd better create a new thread to invoke this method.
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @throws java.io.IOException if I/O operation fails.
|
||||
* @return the actual asset directory
|
||||
*/
|
||||
fun getActualAssetDirectory(assetId: String): File
|
||||
|
||||
/**
|
||||
* Get the asset directory according to the asset id.
|
||||
*/
|
||||
fun getAssetDirectory(assetId: String): File
|
||||
|
||||
/**
|
||||
* Get the file that given asset object refers to
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @param name the asset object name, [AssetIndex.objects.key]
|
||||
* @throws java.io.IOException if I/O operation fails.
|
||||
* @return the file that given asset object refers to
|
||||
*/
|
||||
fun getAssetObject(assetId: String, name: String): File
|
||||
|
||||
/**
|
||||
* Get the file that given asset object refers to
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @param obj the asset object, [AssetIndex.objects]
|
||||
* @return the file that given asset object refers to
|
||||
*/
|
||||
fun getAssetObject(assetId: String, obj: AssetObject): File
|
||||
|
||||
/**
|
||||
* Get asset index that assetId represents
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @return the asset index
|
||||
*/
|
||||
fun getAssetIndex(assetId: String): AssetIndex
|
||||
|
||||
/**
|
||||
* Get logging object
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @param loggingInfo the logging info
|
||||
* @return the file that loggingInfo refers to
|
||||
*/
|
||||
fun getLoggingObject(assetId: String, loggingInfo: LoggingInfo): File
|
||||
}
|
||||
128
HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.kt
Normal file
128
HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.kt
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.JavaVersion
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
data class LaunchOptions(
|
||||
/**
|
||||
* The game directory
|
||||
*/
|
||||
val gameDir: File,
|
||||
|
||||
/**
|
||||
* The Java Environment that Minecraft runs on.
|
||||
*/
|
||||
val java: JavaVersion = JavaVersion.fromCurrentEnvironment(),
|
||||
|
||||
/**
|
||||
* Will shown in the left bottom corner of the main menu of Minecraft.
|
||||
* null if use the id of launch version.
|
||||
*/
|
||||
val versionName: String? = null,
|
||||
|
||||
/**
|
||||
* Don't know what the hell this is.
|
||||
*/
|
||||
var profileName: String? = null,
|
||||
|
||||
/**
|
||||
* User custom additional minecraft command line arguments.
|
||||
*/
|
||||
val minecraftArgs: String? = null,
|
||||
|
||||
/**
|
||||
* User custom additional java virtual machine command line arguments.
|
||||
*/
|
||||
val javaArgs: String? = null,
|
||||
|
||||
/**
|
||||
* The minimum memory that the JVM can allocate.
|
||||
*/
|
||||
val minMemory: Int? = null,
|
||||
|
||||
/**
|
||||
* The maximum memory that the JVM can allocate.
|
||||
*/
|
||||
val maxMemory: Int? = null,
|
||||
|
||||
/**
|
||||
* The maximum metaspace memory that the JVM can allocate.
|
||||
* For Java 7 -XX:PermSize and Java 8 -XX:MetaspaceSize
|
||||
* Containing class instances.
|
||||
*/
|
||||
val metaspace: Int? = null,
|
||||
|
||||
/**
|
||||
* The initial game window width
|
||||
*/
|
||||
val width: Int? = null,
|
||||
|
||||
/**
|
||||
* The initial game window height
|
||||
*/
|
||||
val height: Int? = null,
|
||||
|
||||
/**
|
||||
* Is inital game window fullscreen.
|
||||
*/
|
||||
val fullscreen: Boolean = false,
|
||||
|
||||
/**
|
||||
* The server ip that will connect to when enter game main menu.
|
||||
*/
|
||||
val serverIp: String? = null,
|
||||
|
||||
/**
|
||||
* i.e. optirun
|
||||
*/
|
||||
val wrapper: String? = null,
|
||||
|
||||
/**
|
||||
* The host of the proxy address
|
||||
*/
|
||||
val proxyHost: String? = null,
|
||||
|
||||
/**
|
||||
* the port of the proxy address.
|
||||
*/
|
||||
val proxyPort: String? = null,
|
||||
|
||||
/**
|
||||
* The user name of the proxy, optional.
|
||||
*/
|
||||
val proxyUser: String? = null,
|
||||
|
||||
/**
|
||||
* The password of the proxy, optional
|
||||
*/
|
||||
val proxyPass: String? = null,
|
||||
|
||||
/**
|
||||
* Prevent game launcher from generating default JVM arguments like max memory.
|
||||
*/
|
||||
val noGeneratedJVMArgs: Boolean = false,
|
||||
|
||||
/**
|
||||
* Called command line before launching the game.
|
||||
*/
|
||||
val precalledCommand: String? = null
|
||||
|
||||
) : Serializable
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
@Immutable
|
||||
class LibrariesDownloadInfo(
|
||||
@SerializedName("artifact")
|
||||
val artifact: LibraryDownloadInfo? = null,
|
||||
classifier_: Map<String, LibraryDownloadInfo>? = null
|
||||
) {
|
||||
val classifiers: Map<String, LibraryDownloadInfo>?
|
||||
get() = Collections.unmodifiableMap(classifiersImpl)
|
||||
|
||||
@SerializedName("classifiers")
|
||||
private var classifiersImpl: MutableMap<String, LibraryDownloadInfo>? =
|
||||
if (classifier_ == null || classifier_.isEmpty()) null
|
||||
else HashMap(classifier_)
|
||||
}
|
||||
114
HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.kt
Normal file
114
HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.kt
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 com.google.gson.*
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* A class that describes a Minecraft dependency.
|
||||
*
|
||||
* @see LibraryDeserializer
|
||||
*/
|
||||
@Immutable
|
||||
open class Library(
|
||||
val groupId: String,
|
||||
val artifactId: String,
|
||||
val version: String,
|
||||
classifier_: String? = null,
|
||||
@SerializedName("url")
|
||||
private val url: String? = null,
|
||||
@SerializedName("downloads")
|
||||
private val downloads: LibrariesDownloadInfo? = null,
|
||||
@SerializedName("extract")
|
||||
val extract: ExtractRules? = null,
|
||||
@SerializedName("lateload")
|
||||
val lateload: Boolean = false,
|
||||
private val natives: Map<OS, String>? = null,
|
||||
private val rules: List<CompatibilityRule>? = null
|
||||
) {
|
||||
|
||||
val appliesToCurrentEnvironment: Boolean = CompatibilityRule.appliesToCurrentEnvironment(rules)
|
||||
val isNative: Boolean = natives != null && appliesToCurrentEnvironment
|
||||
val download: LibraryDownloadInfo
|
||||
val classifier: String? = classifier_ ?: natives?.get(OS.CURRENT_OS)?.replace("\${arch}", Platform.PLATFORM.bit)
|
||||
val path: String
|
||||
|
||||
init {
|
||||
|
||||
var temp: LibraryDownloadInfo? = null
|
||||
if (downloads != null) {
|
||||
if (isNative)
|
||||
temp = downloads.classifiers?.get(classifier)
|
||||
else
|
||||
temp = downloads.artifact
|
||||
}
|
||||
path = temp?.path ?: "${groupId.replace(".", "/")}/$artifactId/$version/$artifactId-$version" + (if (classifier == null) "" else "-$classifier") + ".jar"
|
||||
download = LibraryDownloadInfo(
|
||||
url = temp?.url ?: (url ?: DEFAULT_LIBRARY_URL) + path,
|
||||
path = path,
|
||||
size = temp?.size ?: 0,
|
||||
sha1 = temp?.sha1
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString() = "Library[$groupId:$artifactId:$version]"
|
||||
|
||||
companion object LibrarySerializer : JsonDeserializer<Library>, JsonSerializer<Library> {
|
||||
fun fromName(name: String, url: String? = null, downloads: LibrariesDownloadInfo? = null, extract: ExtractRules? = null, natives: Map<OS, String>? = null, rules: List<CompatibilityRule>? = null): Library {
|
||||
val arr = name.split(":".toRegex(), 3)
|
||||
if (arr.size != 3)
|
||||
throw IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version.")
|
||||
return Library(
|
||||
groupId = arr[0].replace("\\", "/"),
|
||||
artifactId = arr[1],
|
||||
version = arr[2],
|
||||
url = url,
|
||||
downloads = downloads,
|
||||
extract = extract,
|
||||
natives = natives,
|
||||
rules = rules
|
||||
)
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Library? {
|
||||
if (json == null || json == JsonNull.INSTANCE)
|
||||
return null
|
||||
val jsonObject = json.asJsonObject
|
||||
if (jsonObject["name"] == null)
|
||||
throw JsonParseException("Library name not found.")
|
||||
return fromName(jsonObject["name"].asString, jsonObject["url"]?.asString, context.deserialize(jsonObject["downloads"], LibrariesDownloadInfo::class.java),
|
||||
context.deserialize(jsonObject["extract"], ExtractRules::class.java),
|
||||
context.deserialize(jsonObject["natives"], typeOf<Map<OS, String>>()),
|
||||
context.deserialize(jsonObject["rules"], typeOf<List<CompatibilityRule>>()))
|
||||
}
|
||||
override fun serialize(src: Library?, typeOfSrc: Type?, context: JsonSerializationContext): JsonElement {
|
||||
if (src == null) return JsonNull.INSTANCE
|
||||
val obj = JsonObject()
|
||||
obj.addProperty("name", "${src.groupId}:${src.artifactId}:${src.version}")
|
||||
obj.addProperty("url", src.url)
|
||||
obj.add("downloads", context.serialize(src.downloads))
|
||||
obj.add("extract", context.serialize(src.extract))
|
||||
obj.add("natives", context.serialize(src.natives, typeOf<Map<OS, String>>()))
|
||||
obj.add("rules", context.serialize(src.rules, typeOf<List<CompatibilityRule>>()))
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
|
||||
@Immutable
|
||||
class LibraryDownloadInfo(
|
||||
url: String = "",
|
||||
sha1: String? = null,
|
||||
size: Int = 0,
|
||||
@SerializedName("path")
|
||||
val path: String? = null
|
||||
): DownloadInfo(url, sha1, size)
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import org.jackhuang.hmcl.util.Validation
|
||||
|
||||
@Immutable
|
||||
class LoggingInfo(
|
||||
@SerializedName("file")
|
||||
val file: IdDownloadInfo = IdDownloadInfo(""),
|
||||
|
||||
@SerializedName("argument")
|
||||
val argument: String = "",
|
||||
|
||||
@SerializedName("type")
|
||||
val type: String = ""
|
||||
): Validation {
|
||||
|
||||
override fun validate() {
|
||||
file.validate()
|
||||
require(argument.isNotBlank(), { "LoggingInfo" })
|
||||
require(type.isNotBlank())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class ReleaseType(val id: String) {
|
||||
@SerializedName("release")
|
||||
RELEASE("release"),
|
||||
@SerializedName("snapshot")
|
||||
SNAPSHOT("snapshot"),
|
||||
@SerializedName("modified")
|
||||
MODIFIED("modified"),
|
||||
@SerializedName("old-beta")
|
||||
OLD_BETA("old-beta"),
|
||||
@SerializedName("old-alpha")
|
||||
OLD_ALPHA("old-alpha"),
|
||||
@SerializedName("unknown")
|
||||
UNKNOWN("unknown")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
open class SimpleVersionProvider() : VersionProvider {
|
||||
protected val versionMap = HashMap<String, Version>()
|
||||
|
||||
override fun getVersion(id: String): Version {
|
||||
return versionMap[id] ?: throw VersionNotFoundException("Version id $id not found")
|
||||
}
|
||||
|
||||
override fun hasVersion(id: String): Boolean {
|
||||
return versionMap.containsKey(id)
|
||||
}
|
||||
|
||||
fun addVersion(version: Version) {
|
||||
versionMap[version.id] = version
|
||||
}
|
||||
}
|
||||
186
HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.kt
Normal file
186
HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.kt
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 com.google.gson.JsonParseException
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.util.*
|
||||
|
||||
@Immutable
|
||||
open class Version(
|
||||
@SerializedName("minecraftArguments")
|
||||
val minecraftArguments: String? = null,
|
||||
@SerializedName("mainClass")
|
||||
val mainClass: String? = null,
|
||||
@SerializedName("time")
|
||||
val time: Date = Date(),
|
||||
@SerializedName("id")
|
||||
val id: String = "",
|
||||
@SerializedName("type")
|
||||
val type: ReleaseType = ReleaseType.UNKNOWN,
|
||||
@SerializedName("releaseTime")
|
||||
val releaseTime: Date = Date(),
|
||||
@SerializedName("jar")
|
||||
val jar: String? = null,
|
||||
@SerializedName("inheritsFrom")
|
||||
val inheritsFrom: String? = null,
|
||||
@SerializedName("assets")
|
||||
private val assets: String? = null,
|
||||
@SerializedName("minimumLauncherVersion")
|
||||
val minimumLauncherVersion: Int = 0,
|
||||
@SerializedName("assetIndex")
|
||||
private val assetIndex: AssetIndexInfo? = null,
|
||||
|
||||
libraries: List<Library> = emptyList(),
|
||||
compatibilityRules: List<CompatibilityRule>? = null,
|
||||
downloads: Map<DownloadType, DownloadInfo>? = null,
|
||||
logging: Map<DownloadType, LoggingInfo>? = null
|
||||
): Comparable<Version>, Validation {
|
||||
val downloads: Map<DownloadType, DownloadInfo>? get() = unmodifiableMap(downloadsImpl)
|
||||
|
||||
@SerializedName("downloads")
|
||||
private val downloadsImpl: MutableMap<DownloadType, DownloadInfo>? = copyMap(downloads)
|
||||
|
||||
val logging: Map<DownloadType, LoggingInfo>? get() = unmodifiableMap(loggingImpl)
|
||||
|
||||
@SerializedName("logging")
|
||||
private val loggingImpl: MutableMap<DownloadType, LoggingInfo>? = copyMap(logging)
|
||||
|
||||
val libraries: List<Library> get() = Collections.unmodifiableList(librariesImpl)
|
||||
|
||||
@SerializedName("libraries")
|
||||
private val librariesImpl: MutableList<Library> = LinkedList(libraries)
|
||||
|
||||
val compatibilityRules: List<CompatibilityRule>? get() = unmodifiableList(compatibilityRulesImpl)
|
||||
|
||||
@SerializedName("compatibilityRules")
|
||||
private val compatibilityRulesImpl: MutableList<CompatibilityRule>? = copyList(compatibilityRules)
|
||||
|
||||
val download: DownloadInfo
|
||||
get() {
|
||||
val client = downloads?.get(DownloadType.CLIENT)
|
||||
val jar = this.jar ?: this.id
|
||||
if (client == null) {
|
||||
return DownloadInfo("$DEFAULT_VERSION_DOWNLOAD_URL$jar/$jar.jar")
|
||||
} else {
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
val actualAssetIndex: AssetIndexInfo
|
||||
get() {
|
||||
val id = assets ?: "legacy"
|
||||
return assetIndex ?: AssetIndexInfo(id = id, url = "$DEFAULT_VERSION_DOWNLOAD_URL$id.json")
|
||||
}
|
||||
|
||||
fun appliesToCurrentEnvironment() = CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules)
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
override fun equals(other: Any?): Boolean =
|
||||
if (other is Version) Objects.equals(this.id, other.id)
|
||||
else false
|
||||
override fun compareTo(other: Version) = id.compareTo(other.id)
|
||||
open fun install(id: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve given version
|
||||
*
|
||||
* @throws CircleDependencyException
|
||||
*/
|
||||
fun resolve(provider: VersionProvider): Version =
|
||||
resolve(provider, HashSet<String>())
|
||||
|
||||
protected open fun resolve(provider: VersionProvider, resolvedSoFar: MutableSet<String>): Version {
|
||||
if (this.inheritsFrom == null)
|
||||
return this
|
||||
if (!resolvedSoFar.add(this.id))
|
||||
throw CircleDependencyException(resolvedSoFar.toString())
|
||||
|
||||
// It is supposed to auto install an version in getVersion.
|
||||
val parent = provider.getVersion(this.inheritsFrom).resolve(provider, resolvedSoFar)
|
||||
return Version(
|
||||
id = this.id,
|
||||
jar = this.jar ?: parent.jar,
|
||||
time = this.time,
|
||||
type = this.type,
|
||||
assets = this.assets ?: parent.assets,
|
||||
logging = this.logging ?: parent.logging,
|
||||
mainClass = this.mainClass ?: parent.mainClass,
|
||||
libraries = merge(this.libraries, parent.libraries),
|
||||
downloads = this.downloads ?: parent.downloads,
|
||||
assetIndex = this.assetIndex ?: parent.assetIndex,
|
||||
releaseTime = this.releaseTime,
|
||||
inheritsFrom = null,
|
||||
minecraftArguments = this.minecraftArguments ?: parent.minecraftArguments,
|
||||
minimumLauncherVersion = parent.minimumLauncherVersion
|
||||
)
|
||||
}
|
||||
|
||||
fun copy(
|
||||
minecraftArguments: String? = this.minecraftArguments,
|
||||
mainClass: String? = this.mainClass,
|
||||
time: Date = this.time,
|
||||
releaseTime: Date = this.releaseTime,
|
||||
id: String = this.id,
|
||||
type: ReleaseType = this.type,
|
||||
jar: String? = this.jar,
|
||||
inheritsFrom: String? = this.inheritsFrom,
|
||||
assets: String? = this.assets,
|
||||
minimumLauncherVersion: Int = this.minimumLauncherVersion,
|
||||
assetIndex: AssetIndexInfo? = this.assetIndex,
|
||||
libraries: List<Library> = this.librariesImpl,
|
||||
compatibilityRules: List<CompatibilityRule>? = this.compatibilityRulesImpl,
|
||||
downloads: Map<DownloadType, DownloadInfo>? = this.downloads,
|
||||
logging: Map<DownloadType, LoggingInfo>? = this.logging) =
|
||||
Version(minecraftArguments,
|
||||
mainClass,
|
||||
time,
|
||||
id,
|
||||
type,
|
||||
releaseTime,
|
||||
jar,
|
||||
inheritsFrom,
|
||||
assets,
|
||||
minimumLauncherVersion,
|
||||
assetIndex,
|
||||
libraries,
|
||||
compatibilityRules,
|
||||
downloads,
|
||||
logging
|
||||
)
|
||||
|
||||
override fun validate() {
|
||||
if (id.isBlank())
|
||||
throw JsonParseException("Version ID cannot be blank")
|
||||
(downloadsImpl as Map<*, *>?)?.forEach { (key, value) ->
|
||||
if (key !is DownloadType)
|
||||
throw JsonParseException("Version downloads key must be DownloadType")
|
||||
if (value !is DownloadInfo)
|
||||
throw JsonParseException("Version downloads value must be DownloadInfo")
|
||||
}
|
||||
(loggingImpl as Map<*, *>?)?.forEach { (key, value) ->
|
||||
if (key !is DownloadType)
|
||||
throw JsonParseException("Version logging key must be DownloadType")
|
||||
if (value !is LoggingInfo)
|
||||
throw JsonParseException("Version logging value must be DownloadInfo")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
class VersionNotFoundException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Supports version accessing.
|
||||
*
|
||||
* @see Version.resolve
|
||||
*/
|
||||
interface VersionProvider {
|
||||
/**
|
||||
* Does the version of id exist?
|
||||
* @param id the id of version
|
||||
* @return true if the version exists
|
||||
*/
|
||||
fun hasVersion(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Get the version
|
||||
* @param id the id of version
|
||||
* @return the version you want
|
||||
*/
|
||||
fun getVersion(id: String): Version
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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.launch
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* @param version A resolved version(calling [Version.resolve])
|
||||
* @param account The user account
|
||||
* @param options The launching configuration
|
||||
*/
|
||||
open class DefaultLauncher(repository: GameRepository, version: Version, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
|
||||
: Launcher(repository, version, account, options, listener, isDaemon) {
|
||||
|
||||
protected val native: File by lazy { repository.getNativeDirectory(version.id) }
|
||||
|
||||
init {
|
||||
if (version.inheritsFrom != null)
|
||||
throw IllegalArgumentException("Version must be resolved")
|
||||
if (version.minecraftArguments == null)
|
||||
throw NullPointerException("Version minecraft argument can not be null")
|
||||
if (version.mainClass == null || version.mainClass.isBlank())
|
||||
throw NullPointerException("Version main class can not be null")
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the [account] must have logged in when calling this property
|
||||
*/
|
||||
override val rawCommandLine: List<String> by lazy {
|
||||
val res = LinkedList<String>()
|
||||
|
||||
// Executable
|
||||
if (options.wrapper != null && options.wrapper.isNotBlank())
|
||||
res.add(options.wrapper)
|
||||
|
||||
res.add(options.java.binary.toString())
|
||||
|
||||
if (options.javaArgs != null && options.javaArgs.isNotBlank())
|
||||
res.addAll(options.javaArgs.tokenize())
|
||||
|
||||
// JVM Args
|
||||
if (!options.noGeneratedJVMArgs) {
|
||||
appendJvmArgs(res)
|
||||
|
||||
res.add("-Dminecraft.client.jar=${repository.getVersionJar(version)}")
|
||||
|
||||
if (OS.CURRENT_OS == OS.OSX) {
|
||||
res.add("-Xdock:name=Minecraft ${version.id}")
|
||||
res.add("-Xdock:icon=" + repository.getAssetObject(version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath);
|
||||
}
|
||||
|
||||
val logging = version.logging
|
||||
if (logging != null) {
|
||||
val loggingInfo = logging[DownloadType.CLIENT]
|
||||
if (loggingInfo != null) {
|
||||
val loggingFile = repository.getLoggingObject(version.actualAssetIndex.id, loggingInfo)
|
||||
if (loggingFile.exists())
|
||||
res.add(loggingInfo.argument.replace("\${path}", loggingFile.absolutePath))
|
||||
}
|
||||
}
|
||||
|
||||
if (OS.CURRENT_OS == OS.WINDOWS)
|
||||
res.add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump")
|
||||
else
|
||||
res.add("-Duser.home=${options.gameDir.parent}")
|
||||
|
||||
if (options.java.version >= JavaVersion.JAVA_7)
|
||||
res.add("-XX:+UseG1GC")
|
||||
else
|
||||
res.add("-Xincgc")
|
||||
|
||||
if (options.metaspace != null && options.metaspace > 0) {
|
||||
if (options.java.version < JavaVersion.JAVA_8)
|
||||
res.add("-XX:PermSize=${options.metaspace}m")
|
||||
else
|
||||
res.add("-XX:MetaspaceSize=${options.metaspace}m")
|
||||
}
|
||||
|
||||
res.add("-XX:-UseAdaptiveSizePolicy")
|
||||
res.add("-XX:-OmitStackTraceInFastThrow")
|
||||
res.add("-Xmn128m")
|
||||
|
||||
if (options.maxMemory != null && options.maxMemory > 0)
|
||||
res.add("-Xmx${options.maxMemory}m")
|
||||
if (options.minMemory != null && options.minMemory > 0)
|
||||
res.add("-Xms${options.minMemory}m")
|
||||
|
||||
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
}
|
||||
|
||||
// Classpath
|
||||
res.add("-Djava.library.path=${native.absolutePath}")
|
||||
|
||||
val lateload = LinkedList<File>()
|
||||
val classpath = StringBuilder()
|
||||
for (library in version.libraries)
|
||||
if (library.appliesToCurrentEnvironment && !library.isNative) {
|
||||
val f = repository.getLibraryFile(version, library)
|
||||
if (f.exists() && f.isFile) {
|
||||
if (library.lateload)
|
||||
lateload += f
|
||||
else classpath.append(f.absolutePath).append(OS.PATH_SEPARATOR)
|
||||
}
|
||||
}
|
||||
for (library in lateload)
|
||||
classpath.append(library.absolutePath).append(OS.PATH_SEPARATOR)
|
||||
|
||||
val jar = repository.getVersionJar(version)
|
||||
if (!jar.exists() || !jar.isFile)
|
||||
throw GameException("Minecraft jar does not exist")
|
||||
classpath.append(jar.absolutePath)
|
||||
res.add("-cp")
|
||||
res.add(classpath.toString())
|
||||
|
||||
// Main Class
|
||||
res.add(version.mainClass!!)
|
||||
|
||||
// Provided Minecraft arguments
|
||||
val gameAssets = repository.getActualAssetDirectory(version.actualAssetIndex.id)
|
||||
|
||||
version.minecraftArguments!!.tokenize().forEach { line ->
|
||||
res.add(line
|
||||
.replace("\${auth_player_name}", account.username)
|
||||
.replace("\${auth_session}", account.authToken)
|
||||
.replace("\${auth_access_token}", account.authToken)
|
||||
.replace("\${auth_uuid}", account.userId)
|
||||
.replace("\${version_name}", options.versionName ?: version.id)
|
||||
.replace("\${profile_name}", options.profileName ?: "Minecraft")
|
||||
.replace("\${version_type}", version.type.id)
|
||||
.replace("\${game_directory}", repository.getRunDirectory(version.id).absolutePath)
|
||||
.replace("\${game_assets}", gameAssets.absolutePath)
|
||||
.replace("\${assets_root}", gameAssets.absolutePath)
|
||||
.replace("\${user_type}", account.userType.toString().toLowerCase())
|
||||
.replace("\${assets_index_name}", version.actualAssetIndex.id)
|
||||
.replace("\${user_properties}", account.userProperties)
|
||||
)
|
||||
}
|
||||
|
||||
// Optional Minecraft arguments
|
||||
if (options.height != null && options.width != null) {
|
||||
res.add("--height");
|
||||
res.add(options.height.toString())
|
||||
res.add("--width");
|
||||
res.add(options.width.toString())
|
||||
}
|
||||
|
||||
if (options.serverIp != null) {
|
||||
val args = options.serverIp.split(":")
|
||||
res.add("--server")
|
||||
res.add(args[0])
|
||||
res.add("--port")
|
||||
res.add(if (args.size > 1) args[1] else "25565")
|
||||
}
|
||||
|
||||
if (options.fullscreen)
|
||||
res.add("--fullscreen")
|
||||
|
||||
if (options.proxyHost != null && options.proxyHost.isNotBlank() &&
|
||||
options.proxyPort != null && options.proxyPort.isNotBlank()) {
|
||||
res.add("--proxyHost");
|
||||
res.add(options.proxyHost)
|
||||
res.add("--proxyPort");
|
||||
res.add(options.proxyPort)
|
||||
if (options.proxyUser != null && options.proxyUser.isNotBlank() &&
|
||||
options.proxyPass != null && options.proxyPass.isNotBlank()) {
|
||||
res.add("--proxyUser");
|
||||
res.add(options.proxyUser);
|
||||
res.add("--proxyPass");
|
||||
res.add(options.proxyPass);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.minecraftArgs != null && options.minecraftArgs.isNotBlank())
|
||||
res.addAll(options.minecraftArgs.tokenize())
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something here.
|
||||
* i.e.
|
||||
* -Dminecraft.launcher.version=<Your launcher name>
|
||||
* -Dminecraft.launcher.brand=<Your launcher version>
|
||||
* -Dlog4j.configurationFile=<Your custom log4j configuration
|
||||
*/
|
||||
protected open fun appendJvmArgs(res: MutableList<String>) {}
|
||||
|
||||
open fun decompressNatives() {
|
||||
version.libraries.filter { it.isNative }.forEach { library ->
|
||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
unzip(zip = repository.getLibraryFile(version, library),
|
||||
dest = native,
|
||||
callback = if (library.extract == null) null
|
||||
else library.extract!!::shouldExtract,
|
||||
ignoreExistsFile = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun launch(): JavaProcess {
|
||||
|
||||
// To guarantee that when failed to generate code, we will not call precalled command
|
||||
val builder = ProcessBuilder(rawCommandLine)
|
||||
|
||||
decompressNatives()
|
||||
|
||||
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
|
||||
val process = Runtime.getRuntime().exec(options.precalledCommand)
|
||||
ignoreException {
|
||||
if (process.isAlive)
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
|
||||
builder.directory(repository.getRunDirectory(version.id))
|
||||
.environment().put("APPDATA", options.gameDir.parent)
|
||||
val p = JavaProcess(builder.start(), rawCommandLine)
|
||||
if (listener == null)
|
||||
startMonitors(p)
|
||||
else
|
||||
startMonitors(p, listener, isDaemon)
|
||||
return p
|
||||
}
|
||||
|
||||
override fun makeLaunchScript(file: String): File {
|
||||
val isWindows = OS.WINDOWS == OS.CURRENT_OS
|
||||
val scriptFile = File(file + (if (isWindows) ".bat" else ".sh"))
|
||||
if (!scriptFile.makeFile())
|
||||
throw IOException("Script file: $scriptFile cannot be created.")
|
||||
scriptFile.bufferedWriter().use { writer ->
|
||||
if (isWindows) {
|
||||
writer.write("@echo off");
|
||||
writer.newLine()
|
||||
writer.write("set APPDATA=" + options.gameDir.parent);
|
||||
writer.newLine()
|
||||
writer.write("cd /D %APPDATA%");
|
||||
writer.newLine()
|
||||
}
|
||||
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
|
||||
writer.write(options.precalledCommand)
|
||||
writer.newLine()
|
||||
}
|
||||
writer.write(makeCommand(rawCommandLine))
|
||||
}
|
||||
if (!scriptFile.setExecutable(true))
|
||||
throw IOException("Cannot make script file '$scriptFile' executable.")
|
||||
return scriptFile
|
||||
}
|
||||
|
||||
private fun startMonitors(javaProcess: JavaProcess) {
|
||||
thread(name = "stdout-pump", isDaemon = true, block = StreamPump(javaProcess.process.inputStream)::run)
|
||||
thread(name = "stderr-pump", isDaemon = true, block = StreamPump(javaProcess.process.errorStream)::run)
|
||||
}
|
||||
|
||||
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
||||
thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, processListener::onLog)::run)
|
||||
thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run)
|
||||
thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run)
|
||||
}
|
||||
}
|
||||
43
HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt
Normal file
43
HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.launch
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.game.LaunchOptions
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.util.JavaProcess
|
||||
import java.io.File
|
||||
|
||||
abstract class Launcher(
|
||||
protected val repository: GameRepository,
|
||||
protected val version: Version,
|
||||
protected val account: AuthInfo,
|
||||
protected val options: LaunchOptions,
|
||||
protected val listener: ProcessListener? = null,
|
||||
protected val isDaemon: Boolean = true) {
|
||||
|
||||
abstract val rawCommandLine: List<String>
|
||||
abstract fun launch(): JavaProcess
|
||||
|
||||
/**
|
||||
* @param file The file path without extension
|
||||
* @return the actual file
|
||||
*/
|
||||
abstract fun makeLaunchScript(file: String): File
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.launch
|
||||
|
||||
interface ProcessListener {
|
||||
/**
|
||||
* Called when receiving a log from stdout
|
||||
*
|
||||
* @param log the log
|
||||
*/
|
||||
fun onLog(log: String)
|
||||
|
||||
/**
|
||||
* Called when receiving a log from stderr.
|
||||
*
|
||||
* @param log the log
|
||||
*/
|
||||
fun onErrorLog(log: String)
|
||||
|
||||
/**
|
||||
* Called when the game process stops.
|
||||
*
|
||||
* @param exitCode the exit code
|
||||
*/
|
||||
fun onExit(exitCode: Int)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.parseParams
|
||||
import org.jackhuang.hmcl.util.readFullyAsString
|
||||
import org.jackhuang.hmcl.util.typeOf
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class ForgeModMetadata(
|
||||
@SerializedName("modid")
|
||||
val modId: String = "",
|
||||
val name: String = "",
|
||||
val description: String = "",
|
||||
val author: String = "",
|
||||
val version: String = "",
|
||||
val mcversion: String = "",
|
||||
val url: String = "",
|
||||
val updateUrl: String = "",
|
||||
val credits: String = "",
|
||||
val authorList: Array<String> = emptyArray(),
|
||||
val authors: Array<String> = emptyArray()
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromFile(modFile: File): ModInfo {
|
||||
ZipFile(modFile).use {
|
||||
val entry = it.getEntry("mcmod.info") ?: throw JsonParseException("File $modFile is not a Forge mod.")
|
||||
val modList: List<ForgeModMetadata>? = GSON.fromJson(it.getInputStream(entry).readFullyAsString(), typeOf<List<ForgeModMetadata>>())
|
||||
val metadata = modList?.firstOrNull() ?: throw JsonParseException("Mod $modFile 'mcmod.info' is malformed")
|
||||
var authors: String = metadata.author
|
||||
if (authors.isBlank() && metadata.authors.isNotEmpty()) {
|
||||
authors = parseParams("", metadata.authors, ", ")
|
||||
}
|
||||
if (authors.isBlank() && metadata.authorList.isNotEmpty()) {
|
||||
authors = parseParams("", metadata.authorList, ", ")
|
||||
}
|
||||
if (authors.isBlank())
|
||||
authors = metadata.credits
|
||||
return ModInfo(
|
||||
file = modFile,
|
||||
name = metadata.name,
|
||||
description = metadata.description,
|
||||
authors = authors,
|
||||
version = metadata.version,
|
||||
mcversion = metadata.mcversion,
|
||||
url = if (metadata.url.isBlank()) metadata.updateUrl else metadata.url
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class LiteModMetadata (
|
||||
val name: String = "",
|
||||
val version: String = "",
|
||||
val mcversion: String = "",
|
||||
val revision: String = "",
|
||||
val author: String = "",
|
||||
val classTransformerClasses: String = "",
|
||||
val description: String = "",
|
||||
val modpackName: String = "",
|
||||
val modpackVersion: String = "",
|
||||
val checkUpdateUrl: String = "",
|
||||
val updateURI: String = ""
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromFile(modFile: File): ModInfo {
|
||||
ZipFile(modFile).use {
|
||||
val entry = it.getEntry("litemod.json")
|
||||
requireNotNull(entry, { "File $modFile is not a LiteLoader mod." })
|
||||
val modList: LiteModMetadata? = GSON.fromJson<LiteModMetadata>(it.getInputStream(entry).readFullyAsString())
|
||||
val metadata = modList ?: throw JsonParseException("Mod $modFile 'litemod.json' is malformed")
|
||||
return ModInfo(
|
||||
file = modFile,
|
||||
name = metadata.name,
|
||||
description = metadata.description,
|
||||
authors = metadata.author,
|
||||
version = metadata.version,
|
||||
mcversion = metadata.mcversion,
|
||||
url = metadata.updateURI
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.kt
Normal file
74
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import java.io.File
|
||||
|
||||
class ModInfo (
|
||||
val file: File,
|
||||
val name: String,
|
||||
val description: String = "",
|
||||
val authors: String = "",
|
||||
val version: String = "",
|
||||
val mcversion: String = "",
|
||||
val url: String = ""
|
||||
): Comparable<ModInfo> {
|
||||
val isActive: Boolean
|
||||
get() = file.extension != DISABLED_EXTENSION
|
||||
|
||||
val fileName: String = (if (isActive) file.name else file.nameWithoutExtension).substringBeforeLast(".")
|
||||
|
||||
override fun compareTo(other: ModInfo): Int {
|
||||
return fileName.compareTo(other.fileName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DISABLED_EXTENSION = "disabled"
|
||||
|
||||
fun isFileMod(file: File): Boolean {
|
||||
var name = file.name
|
||||
val disabled = name.endsWith(".disabled")
|
||||
if (disabled)
|
||||
name = name.substringBeforeLast(".disabled")
|
||||
return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith("litemod")
|
||||
}
|
||||
|
||||
fun fromFile(modFile: File): ModInfo {
|
||||
val file = if (modFile.extension == DISABLED_EXTENSION)
|
||||
modFile.absoluteFile.parentFile.resolve(modFile.nameWithoutExtension)
|
||||
else modFile
|
||||
if (file.extension == "zip" || file.extension == "jar")
|
||||
try {
|
||||
return ForgeModMetadata.fromFile(modFile)
|
||||
} catch (e: JsonParseException) {
|
||||
throw e
|
||||
} catch (ignore: Exception) {}
|
||||
|
||||
else if (file.extension == "litemod")
|
||||
try {
|
||||
return LiteModMetadata.fromFile(modFile)
|
||||
} catch (e: JsonParseException) {
|
||||
throw e
|
||||
} catch (ignore: Exception) {}
|
||||
else throw IllegalArgumentException("File $modFile is not mod")
|
||||
|
||||
return ModInfo(file = modFile, name = modFile.nameWithoutExtension, description = "Unrecognized mod file")
|
||||
}
|
||||
}
|
||||
}
|
||||
68
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.kt
Normal file
68
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.kt
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap
|
||||
import org.jackhuang.hmcl.util.asVersion
|
||||
import org.jackhuang.hmcl.util.ignoreException
|
||||
import org.jackhuang.hmcl.util.makeDirectory
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class ModManager(private val repository: GameRepository) {
|
||||
private val modCache = SimpleMultimap<String, ModInfo>(::HashMap, ::TreeSet)
|
||||
|
||||
fun refreshMods(id: String): Collection<ModInfo> {
|
||||
val modsDirectory = repository.getRunDirectory(id).resolve("mods")
|
||||
val puter = { modFile: File -> ignoreException { modCache.put(id, ModInfo.fromFile(modFile)) } }
|
||||
modsDirectory.listFiles()?.forEach { modFile ->
|
||||
if (modFile.isDirectory && modFile.name.asVersion() != null)
|
||||
modFile.listFiles()?.forEach(puter)
|
||||
puter(modFile)
|
||||
}
|
||||
return modCache[id]
|
||||
}
|
||||
|
||||
fun getMods(id: String) : Collection<ModInfo> {
|
||||
if (!modCache.containsKey(id))
|
||||
refreshMods(id)
|
||||
return modCache[id] ?: emptyList()
|
||||
}
|
||||
|
||||
fun addMod(id: String, file: File) {
|
||||
if (!ModInfo.isFileMod(file))
|
||||
throw IllegalArgumentException("File $file is not a valid mod file.")
|
||||
|
||||
if (!modCache.containsKey(id))
|
||||
refreshMods(id)
|
||||
|
||||
val modsDirectory = repository.getRunDirectory(id).resolve("mods")
|
||||
if (!modsDirectory.makeDirectory())
|
||||
throw IOException("Cannot make directory $modsDirectory")
|
||||
|
||||
val newFile = modsDirectory.resolve(file.name)
|
||||
file.copyTo(newFile)
|
||||
|
||||
modCache.put(id, ModInfo.fromFile(newFile))
|
||||
}
|
||||
|
||||
fun removeMods(id: String, vararg modInfos: ModInfo): Boolean =
|
||||
modInfos.fold(true, { acc, modInfo -> acc && modInfo.file.delete() }).apply { refreshMods(id) }
|
||||
}
|
||||
26
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.kt
Normal file
26
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import java.io.File
|
||||
|
||||
class Modpack(val file: File) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.mod
|
||||
|
||||
object ModpackManager {
|
||||
|
||||
}
|
||||
40
HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt
Normal file
40
HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
|
||||
override val hidden: Boolean = true
|
||||
|
||||
override val dependents: Collection<Task> = listOf(pred)
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
|
||||
override fun execute() {
|
||||
val task = this.succ(pred)
|
||||
if (task != null)
|
||||
dependencies += task
|
||||
}
|
||||
}
|
||||
|
||||
infix fun <T: Task> T.then(b: Task): Task = CoupleTask(this, { b }, true)
|
||||
|
||||
/**
|
||||
* @param b A runnable that decides what to do next, You can also do something here.
|
||||
*/
|
||||
infix fun <T: Task> T.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true)
|
||||
|
||||
infix fun Task.with(b: Task): Task = CoupleTask(this, { b }, false)
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import org.jackhuang.hmcl.event.EventManager
|
||||
import org.jackhuang.hmcl.event.FailedEvent
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import org.jackhuang.hmcl.util.closeQuietly
|
||||
import org.jackhuang.hmcl.util.DigestUtils
|
||||
import org.jackhuang.hmcl.util.makeDirectory
|
||||
import org.jackhuang.hmcl.util.createConnection
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.math.BigInteger
|
||||
import java.util.logging.Level
|
||||
|
||||
class FileDownloadTask(val url: URL, val file: File, val hash: String? = null, val retry: Int = 5, val proxy: Proxy = Proxy.NO_PROXY): Task() {
|
||||
override val scheduler: Scheduler = Scheduler.IO_THREAD
|
||||
|
||||
var onFailed = EventManager<FailedEvent<URL>>()
|
||||
private var rFile: RandomAccessFile? = null
|
||||
private var stream: InputStream? = null
|
||||
|
||||
fun closeFiles() {
|
||||
rFile?.closeQuietly()
|
||||
rFile = null
|
||||
stream?.closeQuietly()
|
||||
stream = null
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
var currentURL = url
|
||||
LOG.finer("Downloading: $currentURL, to: $file")
|
||||
var exception: Exception? = null
|
||||
for (repeat in 0 until retry) {
|
||||
if (repeat > 0) {
|
||||
val event = FailedEvent(this, repeat, currentURL)
|
||||
onFailed(event)
|
||||
if (currentURL != event.newResult) {
|
||||
LOG.fine("Switch from: $currentURL to: ${event.newResult}")
|
||||
currentURL = event.newResult
|
||||
}
|
||||
}
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt()
|
||||
break
|
||||
}
|
||||
|
||||
var temp: File? = null
|
||||
|
||||
try {
|
||||
updateProgress(0.0)
|
||||
|
||||
val conn = url.createConnection(proxy)
|
||||
conn.connect()
|
||||
|
||||
if (conn.responseCode / 100 != 2)
|
||||
throw IOException("Server error, response code: ${conn.responseCode}")
|
||||
|
||||
val contentLength = conn.contentLength
|
||||
if (contentLength < 1)
|
||||
throw IOException("The content length is invalid")
|
||||
|
||||
if (!file.absoluteFile.parentFile.makeDirectory())
|
||||
throw IOException("Could not make directory: ${file.absoluteFile.parent}")
|
||||
|
||||
temp = createTempFile("HMCLCore")
|
||||
rFile = RandomAccessFile(temp, "rw")
|
||||
|
||||
val digest = DigestUtils.sha1Digest
|
||||
|
||||
stream = conn.inputStream
|
||||
var lastDownloaded = 0
|
||||
var downloaded = 0
|
||||
var lastTime = System.currentTimeMillis()
|
||||
val buf = ByteArray(4096)
|
||||
while (true) {
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt()
|
||||
break
|
||||
}
|
||||
|
||||
val read = stream!!.read(buf)
|
||||
if (read == -1)
|
||||
break
|
||||
|
||||
if (hash != null)
|
||||
digest.update(buf, 0, read)
|
||||
|
||||
rFile!!.write(buf, 0, read)
|
||||
downloaded += read
|
||||
|
||||
updateProgress(downloaded, contentLength)
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastTime >= 1000L) {
|
||||
updateMessage(((downloaded - lastDownloaded) / 1024).toString() + "KB/s")
|
||||
lastDownloaded = downloaded
|
||||
lastTime = now
|
||||
}
|
||||
}
|
||||
|
||||
closeFiles()
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
temp.delete()
|
||||
Thread.currentThread().interrupt()
|
||||
break
|
||||
} else {
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
if (!file.absoluteFile.parentFile.makeDirectory())
|
||||
throw IOException("Cannot make parent directory $file")
|
||||
if (!temp.renameTo(file))
|
||||
throw IOException("Cannot move temp file to $file")
|
||||
}
|
||||
|
||||
check(downloaded == contentLength, { "Unexpected file size: $downloaded, expected: $contentLength" })
|
||||
|
||||
if (hash != null) {
|
||||
val hashCode = String.format("%1$040x", BigInteger(1, digest.digest()))
|
||||
check(hash.equals(hashCode, ignoreCase = true), { "Unexpected hash code: $hashCode, expected: $hash" })
|
||||
}
|
||||
|
||||
return
|
||||
} catch(e: Exception) {
|
||||
temp?.delete()
|
||||
exception = e
|
||||
LOG.log(Level.WARNING, "Unable to download file $currentURL", e)
|
||||
} finally {
|
||||
closeFiles()
|
||||
}
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
66
HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.kt
Normal file
66
HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import org.jackhuang.hmcl.util.createConnection
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class GetTask(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult<String>() {
|
||||
override val scheduler: Scheduler = Scheduler.IO_THREAD
|
||||
|
||||
override fun execute() {
|
||||
var exception: IOException? = null
|
||||
for (time in 0 until retry) {
|
||||
if (time > 0)
|
||||
LOG.warning("Unable to finish downloading $url, retrying time: $time")
|
||||
try {
|
||||
updateProgress(0.0)
|
||||
val conn = url.createConnection(proxy)
|
||||
val input = conn.inputStream
|
||||
val baos = ByteArrayOutputStream()
|
||||
val buf = ByteArray(4096)
|
||||
val size = conn.contentLength
|
||||
var read = 0
|
||||
while (true) {
|
||||
val len = input.read(buf)
|
||||
if (len == -1)
|
||||
break
|
||||
read += len
|
||||
|
||||
baos.write(buf, 0, len)
|
||||
updateProgress(read, size)
|
||||
|
||||
if (Thread.currentThread().isInterrupted)
|
||||
return
|
||||
}
|
||||
|
||||
result = baos.toString(encoding.name())
|
||||
return
|
||||
} catch (e: IOException) {
|
||||
exception = e
|
||||
}
|
||||
}
|
||||
if (exception != null)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
class ParallelTask(vararg tasks: Task): Task() {
|
||||
override val hidden: Boolean = true
|
||||
override val dependents: Collection<Task> = listOf(*tasks)
|
||||
|
||||
override fun execute() {}
|
||||
}
|
||||
69
HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt
Normal file
69
HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import javafx.application.Platform
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
interface Scheduler {
|
||||
fun schedule(block: Runnable): Future<*>?
|
||||
|
||||
companion object Schedulers {
|
||||
val IMMEDIATE = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
block.run()
|
||||
return null
|
||||
}
|
||||
}
|
||||
val JAVAFX: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
Platform.runLater(block)
|
||||
return null
|
||||
}
|
||||
}
|
||||
val SWING: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
SwingUtilities.invokeLater(block)
|
||||
return null
|
||||
}
|
||||
}
|
||||
val NEW_THREAD: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable) = CACHED_EXECUTOR.submit(block)
|
||||
}
|
||||
val IO_THREAD: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable) = IO_EXECUTOR.submit(block)
|
||||
}
|
||||
val DEFAULT = NEW_THREAD
|
||||
private val CACHED_EXECUTOR = Executors.newCachedThreadPool()
|
||||
private val IO_EXECUTOR: ExecutorService by lazy {
|
||||
Executors.newFixedThreadPool(6, { r: Runnable ->
|
||||
val thread: Thread = Executors.defaultThreadFactory().newThread(r)
|
||||
thread.isDaemon = true
|
||||
thread
|
||||
})
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
CACHED_EXECUTOR.shutdown()
|
||||
IO_EXECUTOR.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
class SilentException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
24
HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.kt
Normal file
24
HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
internal class SimpleTask(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
|
||||
override fun execute() {
|
||||
runnable()
|
||||
}
|
||||
}
|
||||
123
HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt
Normal file
123
HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import org.jackhuang.hmcl.event.EventManager
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
* Disposable task.
|
||||
*/
|
||||
abstract class Task {
|
||||
/**
|
||||
* True if not logging when executing this task.
|
||||
*/
|
||||
open val hidden: Boolean = false
|
||||
|
||||
/**
|
||||
* The scheduler that decides how this task runs.
|
||||
*/
|
||||
open val scheduler: Scheduler = Scheduler.DEFAULT
|
||||
|
||||
/**
|
||||
* True if requires all dependent tasks finishing successfully.
|
||||
*
|
||||
* **Note** if this field is set false, you are not supposed to invoke [run]
|
||||
*/
|
||||
open val reliant: Boolean = true
|
||||
|
||||
var title: String = this.javaClass.toString()
|
||||
|
||||
/**
|
||||
* @see Thread.isInterrupted
|
||||
* @throws InterruptedException if current thread is interrupted
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
abstract fun execute()
|
||||
|
||||
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute before this task running.
|
||||
*/
|
||||
open val dependents: Collection<Task> = emptySet()
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute after this task running.
|
||||
*/
|
||||
open val dependencies: Collection<Task> = emptySet()
|
||||
|
||||
protected open val progressInterval = 1000L
|
||||
private var lastTime = Long.MIN_VALUE
|
||||
private val progressUpdate = AtomicReference<Double>()
|
||||
val progressProperty = ReadOnlyDoubleWrapper(this, "progress", 0.0)
|
||||
protected fun updateProgress(progress: Int, total: Int) = updateProgress(1.0 * progress / total)
|
||||
protected fun updateProgress(progress: Double) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastTime >= progressInterval) {
|
||||
progressProperty.updateAsync(progress, progressUpdate)
|
||||
lastTime = now
|
||||
}
|
||||
}
|
||||
|
||||
private val messageUpdate = AtomicReference<String>()
|
||||
val messageProperty = ReadOnlyStringWrapper(this, "message", null)
|
||||
protected fun updateMessage(newMessage: String) = messageProperty.updateAsync(newMessage, messageUpdate)
|
||||
|
||||
val onDone = EventManager<TaskEvent>()
|
||||
|
||||
/**
|
||||
* **Note** reliant does not work here, which is always treated as true here.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun run() {
|
||||
dependents.forEach(subTaskRunnable)
|
||||
execute()
|
||||
dependencies.forEach(subTaskRunnable)
|
||||
onDone(TaskEvent(this, this, false))
|
||||
}
|
||||
|
||||
private val subTaskRunnable = { task: Task ->
|
||||
this.messageProperty.bind(task.messageProperty)
|
||||
this.progressProperty.bind(task.progressProperty)
|
||||
task.run()
|
||||
this.messageProperty.unbind()
|
||||
this.progressProperty.unbind()
|
||||
}
|
||||
|
||||
fun executor() = TaskExecutor().submit(this)
|
||||
|
||||
fun subscribe(subscriber: Task) {
|
||||
executor().submit(subscriber).start()
|
||||
}
|
||||
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(closure, scheduler))
|
||||
|
||||
override fun toString(): String {
|
||||
return title
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(closure: () -> Unit, scheduler: Scheduler = Scheduler.DEFAULT): Task = SimpleTask(closure, scheduler)
|
||||
fun <V> of(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
internal class TaskCallable<V>(private val callable: Callable<V>) : TaskResult<V>() {
|
||||
override fun execute() {
|
||||
result = callable.call()
|
||||
}
|
||||
}
|
||||
22
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.kt
Normal file
22
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import java.util.*
|
||||
|
||||
class TaskEvent(source: Any, val task: Task, val failed: Boolean) : EventObject(source)
|
||||
156
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt
Normal file
156
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class TaskExecutor() {
|
||||
var taskListener: TaskListener? = null
|
||||
|
||||
var canceled = false
|
||||
private set
|
||||
private val totTask = AtomicInteger(0)
|
||||
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
||||
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
||||
|
||||
/**
|
||||
* Submit a task to subscription to run.
|
||||
* You can submit a task even when started this subscription.
|
||||
* Thread-safe function.
|
||||
*/
|
||||
fun submit(task: Task): TaskExecutor {
|
||||
taskQueue.add(task)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the subscription and run all registered tasks asynchronously.
|
||||
*/
|
||||
fun start() {
|
||||
thread {
|
||||
totTask.addAndGet(taskQueue.size)
|
||||
while (!taskQueue.isEmpty() && !canceled) {
|
||||
val task = taskQueue.poll()
|
||||
if (task != null) {
|
||||
val future = task.scheduler.schedule(Runnable { executeTask(task) })
|
||||
try {
|
||||
future?.get()
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canceled)
|
||||
taskListener?.onTerminate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the subscription ant interrupt all tasks.
|
||||
*/
|
||||
fun cancel() {
|
||||
canceled = true
|
||||
|
||||
while (!workerQueue.isEmpty())
|
||||
workerQueue.poll()?.cancel(true)
|
||||
}
|
||||
|
||||
private fun executeTasks(tasks: Collection<Task>): Boolean {
|
||||
if (tasks.isEmpty())
|
||||
return true
|
||||
|
||||
totTask.addAndGet(tasks.size)
|
||||
val success = AtomicBoolean(true)
|
||||
val counter = CountDownLatch(tasks.size)
|
||||
for (task in tasks) {
|
||||
if (canceled)
|
||||
return false
|
||||
val invoker = Invoker(task, counter, success)
|
||||
val future = task.scheduler.schedule(invoker)
|
||||
if (future != null)
|
||||
workerQueue.add(future)
|
||||
}
|
||||
try {
|
||||
counter.await()
|
||||
return success.get()
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
// Once interrupted, we are aborting the subscription.
|
||||
// and operations fail.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeTask(t: Task): Boolean {
|
||||
if (canceled)
|
||||
return false
|
||||
|
||||
if (!t.hidden)
|
||||
LOG.fine("Executing task: ${t.title}")
|
||||
taskListener?.onReady(t)
|
||||
val doDependentsSucceeded = executeTasks(t.dependents)
|
||||
var flag = false
|
||||
try {
|
||||
if (!doDependentsSucceeded && t.reliant)
|
||||
throw SilentException()
|
||||
|
||||
t.execute()
|
||||
flag = true
|
||||
if (!t.hidden)
|
||||
LOG.finer("Task finished: ${t.title}")
|
||||
executeTasks(t.dependencies)
|
||||
if (!t.hidden) {
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = false))
|
||||
taskListener?.onFinished(t)
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
if (!t.hidden) {
|
||||
LOG.log(Level.FINE, "Task aborted: ${t.title}", e)
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = true))
|
||||
taskListener?.onFailed(t)
|
||||
}
|
||||
} catch (e: SilentException) {
|
||||
// nothing here
|
||||
} catch (e: Exception) {
|
||||
if (!t.hidden) {
|
||||
LOG.log(Level.SEVERE, "Task failed: ${t.title}", e)
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = true))
|
||||
taskListener?.onFailed(t)
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Runnable {
|
||||
override fun run() {
|
||||
try {
|
||||
Thread.currentThread().name = task.title
|
||||
if (!executeTask(task))
|
||||
boolean.set(false)
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
import java.util.*
|
||||
|
||||
interface TaskListener : EventListener {
|
||||
fun onReady(task: Task)
|
||||
fun onFinished(task: Task)
|
||||
fun onFailed(task: Task)
|
||||
fun onTerminate()
|
||||
}
|
||||
22
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskResult.kt
Normal file
22
HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskResult.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.task
|
||||
|
||||
abstract class TaskResult<V> : Task() {
|
||||
open var result: V? = null
|
||||
}
|
||||
22
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Annotation.kt
Normal file
22
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Annotation.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Immutable
|
||||
47
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.kt
Normal file
47
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import javafx.application.Platform
|
||||
import java.awt.EventQueue
|
||||
import java.nio.charset.Charset
|
||||
|
||||
val DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/"
|
||||
val DEFAULT_VERSION_DOWNLOAD_URL = "http://s3.amazonaws.com/Minecraft.Download/versions/"
|
||||
val DEFAULT_INDEX_URL = "http://s3.amazonaws.com/Minecraft.Download/indexes/"
|
||||
|
||||
val SWING_UI_THREAD_SCHEDULER = { runnable: () -> Unit ->
|
||||
if (EventQueue.isDispatchThread())
|
||||
runnable()
|
||||
else
|
||||
EventQueue.invokeLater(runnable)
|
||||
}
|
||||
|
||||
val JAVAFX_UI_THREAD_SCHEDULER = { runnable: () -> Unit ->
|
||||
if (Platform.isFxApplicationThread())
|
||||
runnable()
|
||||
else
|
||||
Platform.runLater(runnable)
|
||||
}
|
||||
|
||||
val UI_THREAD_SCHEDULER: (() -> Unit) -> Unit = { }
|
||||
|
||||
val DEFAULT_ENCODING = "UTF-8"
|
||||
|
||||
val DEFAULT_CHARSET = Charsets.UTF_8
|
||||
val SYSTEM_CHARSET: Charset by lazy { charset(OS.ENCODING) }
|
||||
242
HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.kt
Normal file
242
HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.kt
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
object DigestUtils {
|
||||
|
||||
private val STREAM_BUFFER_LENGTH = 1024
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun digest(digest: MessageDigest, data: InputStream): ByteArray {
|
||||
return updateDigest(digest, data).digest()
|
||||
}
|
||||
|
||||
fun getDigest(algorithm: String): MessageDigest {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw IllegalArgumentException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val md2Digest: MessageDigest
|
||||
get() = getDigest("MD2")
|
||||
|
||||
val md5Digest: MessageDigest
|
||||
get() = getDigest("MD5")
|
||||
|
||||
val sha1Digest: MessageDigest
|
||||
get() = getDigest("SHA-1")
|
||||
|
||||
val sha256Digest: MessageDigest
|
||||
get() = getDigest("SHA-256")
|
||||
|
||||
val sha384Digest: MessageDigest
|
||||
get() = getDigest("SHA-384")
|
||||
|
||||
val sha512Digest: MessageDigest
|
||||
get() = getDigest("SHA-512")
|
||||
|
||||
fun md2(data: ByteArray): ByteArray {
|
||||
return md2Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun md2(data: InputStream): ByteArray {
|
||||
return digest(md2Digest, data)
|
||||
}
|
||||
|
||||
fun md2(data: String): ByteArray {
|
||||
return md2(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun md2Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(md2(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun md2Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(md2(data))
|
||||
}
|
||||
|
||||
fun md2Hex(data: String): String {
|
||||
return Hex.encodeHexString(md2(data))
|
||||
}
|
||||
|
||||
fun md5(data: ByteArray): ByteArray {
|
||||
return md5Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun md5(data: InputStream): ByteArray {
|
||||
return digest(md5Digest, data)
|
||||
}
|
||||
|
||||
fun md5(data: String): ByteArray {
|
||||
return md5(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun md5Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(md5(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun md5Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(md5(data))
|
||||
}
|
||||
|
||||
fun md5Hex(data: String): String {
|
||||
return Hex.encodeHexString(md5(data))
|
||||
}
|
||||
|
||||
fun sha1(data: ByteArray): ByteArray {
|
||||
return sha1Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha1(data: InputStream): ByteArray {
|
||||
return digest(sha1Digest, data)
|
||||
}
|
||||
|
||||
fun sha1(data: String): ByteArray {
|
||||
return sha1(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun sha1Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(sha1(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha1Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(sha1(data))
|
||||
}
|
||||
|
||||
fun sha1Hex(data: String): String {
|
||||
return Hex.encodeHexString(sha1(data))
|
||||
}
|
||||
|
||||
fun sha256(data: ByteArray): ByteArray {
|
||||
return sha256Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha256(data: InputStream): ByteArray {
|
||||
return digest(sha256Digest, data)
|
||||
}
|
||||
|
||||
fun sha256(data: String): ByteArray {
|
||||
return sha256(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun sha256Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(sha256(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha256Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(sha256(data))
|
||||
}
|
||||
|
||||
fun sha256Hex(data: String): String {
|
||||
return Hex.encodeHexString(sha256(data))
|
||||
}
|
||||
|
||||
fun sha384(data: ByteArray): ByteArray {
|
||||
return sha384Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha384(data: InputStream): ByteArray {
|
||||
return digest(sha384Digest, data)
|
||||
}
|
||||
|
||||
fun sha384(data: String): ByteArray {
|
||||
return sha384(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun sha384Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(sha384(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha384Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(sha384(data))
|
||||
}
|
||||
|
||||
fun sha384Hex(data: String): String {
|
||||
return Hex.encodeHexString(sha384(data))
|
||||
}
|
||||
|
||||
fun sha512(data: ByteArray): ByteArray {
|
||||
return sha512Digest.digest(data)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha512(data: InputStream): ByteArray {
|
||||
return digest(sha512Digest, data)
|
||||
}
|
||||
|
||||
fun sha512(data: String): ByteArray {
|
||||
return sha512(data.toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
fun sha512Hex(data: ByteArray): String {
|
||||
return Hex.encodeHexString(sha512(data))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun sha512Hex(data: InputStream): String {
|
||||
return Hex.encodeHexString(sha512(data))
|
||||
}
|
||||
|
||||
fun sha512Hex(data: String): String {
|
||||
return Hex.encodeHexString(sha512(data))
|
||||
}
|
||||
|
||||
fun updateDigest(messageDigest: MessageDigest, valueToDigest: ByteArray): MessageDigest {
|
||||
messageDigest.update(valueToDigest)
|
||||
return messageDigest
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun updateDigest(digest: MessageDigest, data: InputStream): MessageDigest {
|
||||
val buffer = ByteArray(STREAM_BUFFER_LENGTH)
|
||||
var read = data.read(buffer, 0, STREAM_BUFFER_LENGTH)
|
||||
|
||||
while (read > -1) {
|
||||
digest.update(buffer, 0, read)
|
||||
read = data.read(buffer, 0, STREAM_BUFFER_LENGTH)
|
||||
}
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
fun updateDigest(messageDigest: MessageDigest, valueToDigest: String): MessageDigest {
|
||||
messageDigest.update(valueToDigest.toByteArray(Charsets.UTF_8))
|
||||
return messageDigest
|
||||
}
|
||||
}
|
||||
33
HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExitWaiter.kt
Normal file
33
HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExitWaiter.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
/**
|
||||
* @param process the process to wait for
|
||||
* @param watcher the callback that will be called after process stops.
|
||||
*/
|
||||
internal class ExitWaiter(val process: Process, val watcher: (Int) -> Unit) : Runnable {
|
||||
override fun run() {
|
||||
try {
|
||||
process.waitFor()
|
||||
watcher(process.exitValue())
|
||||
} catch (e: InterruptedException) {
|
||||
watcher(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
49
HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.kt
Normal file
49
HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
fun File.makeDirectory(): Boolean = isDirectory || mkdirs()
|
||||
|
||||
fun File.makeFile(): Boolean {
|
||||
if (!absoluteFile.parentFile.makeDirectory())
|
||||
return false
|
||||
if (!exists() && !createNewFile())
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
fun File.isSymlink(): Boolean {
|
||||
if (File.separatorChar == '\\')
|
||||
return false
|
||||
val fileInCanonicalDir: File =
|
||||
if (parent == null) this
|
||||
else File(parentFile.canonicalFile, name)
|
||||
return fileInCanonicalDir.canonicalFile != fileInCanonicalDir.absoluteFile
|
||||
}
|
||||
|
||||
fun File.listFilesByExtension(ext: String): List<File> {
|
||||
val list = mutableListOf<File>()
|
||||
this.listFiles()?.filter { it.extension == ext }?.forEach { list.add(it) }
|
||||
return list
|
||||
}
|
||||
192
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Gson.kt
Normal file
192
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Gson.kt
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import com.google.gson.*
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.lang.reflect.Type
|
||||
import com.google.gson.stream.JsonToken
|
||||
import java.io.IOException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
val GSON: Gson = GsonBuilder()
|
||||
.enableComplexMapKeySerialization()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(Library::class.java, Library)
|
||||
.registerTypeAdapter(Date::class.java, DateTypeAdapter)
|
||||
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
|
||||
.registerTypeAdapterFactory(ValidationTypeAdapterFactory)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory)
|
||||
.create()
|
||||
|
||||
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)
|
||||
|
||||
/**
|
||||
* Check if the json object's fields automatically filled by Gson are in right format.
|
||||
*/
|
||||
interface Validation {
|
||||
/**
|
||||
* 1. Check some non-null fields and;
|
||||
* 2. Check strings and;
|
||||
* 3. Check generic type of lists <T> and maps <K, V> are correct.
|
||||
*
|
||||
* Will be called immediately after initialization.
|
||||
* Throw an exception when values are malformed.
|
||||
* @throws JsonParseException if fields are filled in wrong format or wrong type.
|
||||
*/
|
||||
fun validate()
|
||||
}
|
||||
|
||||
object ValidationTypeAdapterFactory : TypeAdapterFactory {
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T?>?): TypeAdapter<T?> {
|
||||
val delgate = gson.getDelegateAdapter(this, type)
|
||||
return object : TypeAdapter<T?>() {
|
||||
override fun write(out: JsonWriter?, value: T?) {
|
||||
if (value is Validation)
|
||||
value.validate()
|
||||
delgate.write(out, value)
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader?): T? {
|
||||
val value = delgate.read(reader)
|
||||
if (value is Validation)
|
||||
value.validate()
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object LowerCaseEnumTypeAdapterFactory : TypeAdapterFactory {
|
||||
override fun <T> create(gson: Gson, type: TypeToken<T?>): TypeAdapter<T?>? {
|
||||
val rawType = type.rawType
|
||||
if (!rawType.isEnum) {
|
||||
return null
|
||||
}
|
||||
val lowercaseToConstant = HashMap<String, T>()
|
||||
for (constant in rawType.enumConstants) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
lowercaseToConstant.put(toLowercase(constant!!), constant as T)
|
||||
}
|
||||
return object : TypeAdapter<T?>() {
|
||||
@Throws(IOException::class)
|
||||
override fun write(out: JsonWriter, value: T?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
} else {
|
||||
out.value(toLowercase(value))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(reader: JsonReader): T? {
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull()
|
||||
return null
|
||||
}
|
||||
return lowercaseToConstant[reader.nextString().toLowerCase()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toLowercase(o: Any): String {
|
||||
return o.toString().toLowerCase(Locale.US)
|
||||
}
|
||||
}
|
||||
|
||||
object UUIDTypeAdapter : TypeAdapter<UUID>() {
|
||||
override fun read(reader: JsonReader): UUID {
|
||||
return fromString(reader.nextString())
|
||||
}
|
||||
|
||||
override fun write(writer: JsonWriter, value: UUID?) {
|
||||
writer.value(if (value == null) null else fromUUID(value))
|
||||
}
|
||||
|
||||
fun fromUUID(value: UUID): String {
|
||||
return value.toString().replace("-", "")
|
||||
}
|
||||
|
||||
fun fromString(input: String): UUID {
|
||||
return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})".toRegex(), "$1-$2-$3-$4-$5"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object DateTypeAdapter : JsonSerializer<Date>, JsonDeserializer<Date> {
|
||||
private val enUsFormat: DateFormat = DateFormat.getDateTimeInstance(2, 2, Locale.US)
|
||||
private val iso8601Format: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Date {
|
||||
if (json !is JsonPrimitive) {
|
||||
throw JsonParseException("The date should be a string value")
|
||||
} else {
|
||||
val date = this.deserializeToDate(json.getAsString())
|
||||
if (typeOfT === Date::class.java) {
|
||||
return date
|
||||
} else {
|
||||
throw IllegalArgumentException(this.javaClass.toString() + " cannot deserialize to " + typeOfT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
synchronized(this.enUsFormat) {
|
||||
return JsonPrimitive(this.serializeToString(src))
|
||||
}
|
||||
}
|
||||
|
||||
fun deserializeToDate(string: String): Date {
|
||||
synchronized(this.enUsFormat) {
|
||||
try {
|
||||
return this.enUsFormat.parse(string)
|
||||
} catch (ex1: ParseException) {
|
||||
try {
|
||||
return this.iso8601Format.parse(string)
|
||||
} catch (ex2: ParseException) {
|
||||
try {
|
||||
var cleaned = string.replace("Z", "+00:00")
|
||||
cleaned = cleaned.substring(0, 22) + cleaned.substring(23)
|
||||
return this.iso8601Format.parse(cleaned)
|
||||
} catch (e: Exception) {
|
||||
throw JsonSyntaxException("Invalid date: " + string, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun serializeToString(date: Date): String {
|
||||
synchronized(this.enUsFormat) {
|
||||
val result = this.iso8601Format.format(date)
|
||||
return result.substring(0, 22) + ":" + result.substring(22)
|
||||
}
|
||||
}
|
||||
}
|
||||
121
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.kt
Normal file
121
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.kt
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class Hex @JvmOverloads constructor(val charset: Charset = DEFAULT_CHARSET) {
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun decode(array: ByteArray): ByteArray {
|
||||
return decodeHex(String(array, charset).toCharArray())
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun decode(`object`: Any): Any {
|
||||
try {
|
||||
val charArray = (`object` as? String)?.toCharArray() ?: `object` as CharArray
|
||||
return decodeHex(charArray)
|
||||
} catch (e: ClassCastException) {
|
||||
throw Exception(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun encode(array: ByteArray): ByteArray {
|
||||
return encodeHexString(array).toByteArray(charset)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun encode(`object`: Any): Any {
|
||||
try {
|
||||
val byteArray = (`object` as? String)?.toByteArray(charset) ?: `object` as ByteArray
|
||||
|
||||
return encodeHex(byteArray)
|
||||
} catch (e: ClassCastException) {
|
||||
throw Exception(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val charsetName: String
|
||||
get() = this.charset.name()
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString() + "[charsetName=$charset]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val DIGITS_LOWER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
|
||||
private val DIGITS_UPPER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun decodeHex(data: CharArray): ByteArray {
|
||||
val len = data.size
|
||||
|
||||
if (len and 0x1 != 0)
|
||||
throw Exception("Odd number of characters.")
|
||||
|
||||
val out = ByteArray(len shr 1)
|
||||
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (j < len) {
|
||||
var f = toDigit(data[j], j) shl 4
|
||||
j++
|
||||
f = f or toDigit(data[j], j)
|
||||
j++
|
||||
out[i] = (f and 0xFF).toByte()
|
||||
i++
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@JvmOverloads fun encodeHex(data: ByteArray, toLowerCase: Boolean = true): CharArray {
|
||||
return encodeHex(data, if (toLowerCase) DIGITS_LOWER else DIGITS_UPPER)
|
||||
}
|
||||
|
||||
protected fun encodeHex(data: ByteArray, toDigits: CharArray): CharArray {
|
||||
val l = data.size
|
||||
val out = CharArray(l shl 1)
|
||||
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (i < l) {
|
||||
out[j++] = toDigits[(0xF0 and data[i].toInt()).ushr(4)]
|
||||
out[j++] = toDigits[0xF and data[i].toInt()]
|
||||
i++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fun encodeHexString(data: ByteArray): String {
|
||||
return String(encodeHex(data))
|
||||
}
|
||||
|
||||
protected fun toDigit(ch: Char, index: Int): Int {
|
||||
val digit = Character.digit(ch, 16)
|
||||
if (digit == -1)
|
||||
throw IllegalArgumentException("Illegal hexadecimal character $ch at index $index")
|
||||
return digit
|
||||
}
|
||||
}
|
||||
}
|
||||
91
HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.kt
Normal file
91
HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.kt
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
|
||||
const val MAX_BUFFER_SIZE = 4096
|
||||
|
||||
fun Closeable.closeQuietly() {
|
||||
try {
|
||||
this.close()
|
||||
} catch (ex: IOException) {}
|
||||
}
|
||||
|
||||
fun InputStream.readFully(): ByteArrayOutputStream {
|
||||
try {
|
||||
val ans = ByteArrayOutputStream()
|
||||
copyTo(ans)
|
||||
return ans
|
||||
} finally {
|
||||
this.closeQuietly()
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.readFullyAsByteArray(): ByteArray =
|
||||
readFully().toByteArray()
|
||||
|
||||
fun InputStream.readFullyAsString(): String =
|
||||
readFully().toString()
|
||||
|
||||
fun InputStream.readFullyAsString(charset: Charset): String =
|
||||
readFully().toString(charset.name())
|
||||
|
||||
fun InputStream.copyTo(dest: OutputStream, buf: ByteArray) {
|
||||
while (true) {
|
||||
val len = read(buf)
|
||||
if (len == -1)
|
||||
break
|
||||
dest.write(buf, 0, len)
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.copyToAndClose(dest: OutputStream) {
|
||||
this.use { input ->
|
||||
dest.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun makeCommand(cmd: List<String>): String {
|
||||
val cmdbuf = StringBuilder(120)
|
||||
for (i in cmd.indices) {
|
||||
if (i > 0)
|
||||
cmdbuf.append(' ')
|
||||
val s = cmd[i]
|
||||
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)
|
||||
if (s[0] != '"') {
|
||||
cmdbuf.append('"')
|
||||
cmdbuf.append(s)
|
||||
if (s.endsWith("\\"))
|
||||
cmdbuf.append("\\")
|
||||
cmdbuf.append('"')
|
||||
} else if (s.endsWith("\""))
|
||||
// The argument has already been quoted.
|
||||
cmdbuf.append(s)
|
||||
else
|
||||
// Unmatched quote for the argument.
|
||||
throw IllegalArgumentException()
|
||||
else
|
||||
cmdbuf.append(s)
|
||||
}
|
||||
|
||||
return cmdbuf.toString()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
class JavaProcess(
|
||||
val process: Process,
|
||||
val commands: List<String>
|
||||
) {
|
||||
val stdOutLines: List<String> = Collections.synchronizedList(LinkedList<String>())
|
||||
val isRunning: Boolean = try {
|
||||
process.exitValue()
|
||||
true
|
||||
} catch (ex: IllegalThreadStateException) {
|
||||
false
|
||||
}
|
||||
val exitCode: Int get() = process.exitValue()
|
||||
|
||||
override fun toString(): String {
|
||||
return "JavaProcess[commands=$commands, isRunning=$isRunning]"
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.util.regex.Pattern
|
||||
|
||||
data class JavaVersion internal constructor(
|
||||
val binary: File,
|
||||
val version: Int,
|
||||
val platform: Platform) : Serializable
|
||||
{
|
||||
companion object {
|
||||
private val regex = Pattern.compile("java version \"(?<version>[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"")
|
||||
|
||||
val UNKNOWN: Int = -1
|
||||
val JAVA_5: Int = 50
|
||||
val JAVA_6: Int = 60
|
||||
val JAVA_7: Int = 70
|
||||
val JAVA_8: Int = 80
|
||||
val JAVA_9: Int = 90
|
||||
val JAVA_X: Int = 100
|
||||
|
||||
private fun parseVersion(version: String): Int {
|
||||
with(version) {
|
||||
if (startsWith("10") || startsWith("X")) return JAVA_X
|
||||
else if (contains("1.9.") || startsWith("9")) return JAVA_9
|
||||
else if (contains("1.8.")) return JAVA_8
|
||||
else if (contains("1.7.")) return JAVA_7
|
||||
else if (contains("1.6.")) return JAVA_6
|
||||
else if (contains("1.5.")) return JAVA_5
|
||||
else return UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun fromExecutable(file: File): JavaVersion {
|
||||
var platform = Platform.BIT_32
|
||||
var version: String? = null
|
||||
try {
|
||||
val process = ProcessBuilder(file.absolutePath, "-version").start()
|
||||
process.waitFor()
|
||||
process.inputStream.bufferedReader().forEachLine { line ->
|
||||
val m = regex.matcher(line)
|
||||
if (m.find())
|
||||
version = m.group("version")
|
||||
if (line.contains("64-Bit"))
|
||||
platform = Platform.BIT_64
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
throw IOException("Java process is interrupted", e)
|
||||
}
|
||||
val thisVersion = version ?: throw IOException("Java version not matched")
|
||||
val parsedVersion = parseVersion(thisVersion)
|
||||
if (parsedVersion == UNKNOWN)
|
||||
throw IOException("Java version '$thisVersion' can not be recognized")
|
||||
return JavaVersion(file.parentFile, parsedVersion, platform)
|
||||
}
|
||||
|
||||
fun getJavaFile(home: File): File {
|
||||
var path = home.resolve("bin")
|
||||
var javaw = path.resolve("javaw.exe")
|
||||
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
|
||||
return javaw
|
||||
else
|
||||
return path.resolve("java")
|
||||
}
|
||||
|
||||
fun fromCurrentEnvironment(): JavaVersion {
|
||||
return JavaVersion(
|
||||
binary = getJavaFile(File(System.getProperty("java.home"))),
|
||||
version = parseVersion(System.getProperty("java.version")),
|
||||
platform = Platform.PLATFORM)
|
||||
}
|
||||
}
|
||||
}
|
||||
132
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.kt
Normal file
132
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.kt
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import javafx.beans.property.Property
|
||||
import javafx.beans.value.ObservableValue
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.collections.HashMap
|
||||
import sun.text.normalizer.UTF16.append
|
||||
import java.lang.reflect.Array.getLength
|
||||
|
||||
inline fun ignoreException(func: () -> Unit) {
|
||||
try {
|
||||
func()
|
||||
} catch(ignore: Exception) {}
|
||||
}
|
||||
|
||||
inline fun ignoreThrowable(func: () -> Unit) {
|
||||
try {
|
||||
func()
|
||||
} catch (ignore: Throwable) {}
|
||||
}
|
||||
|
||||
fun <K, V> unmodifiableMap(map: Map<K, V>?): Map<K, V>? =
|
||||
if (map == null) null
|
||||
else Collections.unmodifiableMap(map)
|
||||
|
||||
fun <K, V> copyMap(map: Map<K, V>?): MutableMap<K, V>? =
|
||||
if (map == null) null
|
||||
else HashMap(map)
|
||||
|
||||
fun <T> unmodifiableList(list: List<T>?): List<T>? =
|
||||
if (list == null) null
|
||||
else Collections.unmodifiableList(list)
|
||||
|
||||
fun <T> copyList(list: List<T>?): MutableList<T>? =
|
||||
if (list == null) null
|
||||
else LinkedList(list)
|
||||
|
||||
fun <T> merge(vararg c: Collection<T>): List<T> = LinkedList<T>().apply {
|
||||
for (a in c)
|
||||
addAll(a)
|
||||
}
|
||||
|
||||
fun isBlank(str: String?) = str?.isBlank() ?: true
|
||||
fun isNotBlank(str: String?) = !isBlank(str)
|
||||
|
||||
fun String.tokenize(delim: String = " \t\n\r"): List<String> {
|
||||
val list = mutableListOf<String>()
|
||||
val tokenizer = StringTokenizer(this, delim)
|
||||
while (tokenizer.hasMoreTokens())
|
||||
list.add(tokenizer.nextToken())
|
||||
return list
|
||||
}
|
||||
|
||||
fun String.asVersion(): String? {
|
||||
if (count { it != '.' && (it < '0' || it > '9') } > 0 || isBlank())
|
||||
return null
|
||||
val s = split(".")
|
||||
for (i in s) if (i.isBlank()) return null
|
||||
val builder = StringBuilder()
|
||||
var last = s.size - 1
|
||||
for (i in s.size - 1 downTo 0)
|
||||
if (s[i].toInt() == 0)
|
||||
last = i
|
||||
for (i in 0 .. last)
|
||||
builder.append(s[i]).append('.')
|
||||
return builder.deleteCharAt(builder.length - 1).toString()
|
||||
}
|
||||
|
||||
|
||||
fun parseParams(addBefore: String, objects: Collection<*>, addAfter: String): String {
|
||||
return parseParams(addBefore, objects.toTypedArray(), addAfter)
|
||||
}
|
||||
|
||||
fun parseParams(addBefore: String, objects: Array<*>, addAfter: String): String {
|
||||
return parseParams({ addBefore }, objects, { addAfter })
|
||||
}
|
||||
|
||||
fun parseParams(beforeFunc: (Any?) -> String, params: Array<*>?, afterFunc: (Any?) -> String): String {
|
||||
if (params == null)
|
||||
return ""
|
||||
val sb = StringBuilder()
|
||||
for (i in params.indices) {
|
||||
val param = params[i]
|
||||
val addBefore = beforeFunc(param)
|
||||
val addAfter = afterFunc(param)
|
||||
if (i > 0)
|
||||
sb.append(addAfter).append(addBefore)
|
||||
if (param == null)
|
||||
sb.append("null")
|
||||
else if (param.javaClass.isArray) {
|
||||
sb.append("[")
|
||||
if (param is Array<*>) {
|
||||
sb.append(parseParams(beforeFunc, param, afterFunc))
|
||||
} else
|
||||
for (j in 0..java.lang.reflect.Array.getLength(param) - 1) {
|
||||
if (j > 0)
|
||||
sb.append(addAfter)
|
||||
sb.append(addBefore).append(java.lang.reflect.Array.get(param, j))
|
||||
}
|
||||
sb.append("]")
|
||||
} else
|
||||
sb.append(addBefore).append(params[i])
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun <T> Property<in T>.updateAsync(newValue: T, update: AtomicReference<T>) {
|
||||
if (update.getAndSet(newValue) == null) {
|
||||
UI_THREAD_SCHEDULER {
|
||||
val current = update.getAndSet(null)
|
||||
this.value = current
|
||||
}
|
||||
}
|
||||
}
|
||||
32
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lib.kt
Normal file
32
HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lib.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import javafx.beans.value.*
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
|
||||
fun <T> ObservableValue<T>.onChange(op: (T?) -> Unit) = apply { addListener { _, _, new -> op(new) } }
|
||||
fun ObservableBooleanValue.onChange(op: (Boolean) -> Unit) = apply { addListener { _, _, new -> op(new ?: false) } }
|
||||
fun ObservableIntegerValue.onChange(op: (Int) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0).toInt()) } }
|
||||
fun ObservableLongValue.onChange(op: (Long) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0L).toLong()) } }
|
||||
fun ObservableFloatValue.onChange(op: (Float) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0f).toFloat()) } }
|
||||
fun ObservableDoubleValue.onChange(op: (Double) -> Unit) = apply { addListener { _, _, new -> op((new ?: 0.0).toDouble()) } }
|
||||
fun <T> ObservableList<T>.onChange(op: (ListChangeListener.Change<out T>) -> Unit) = apply {
|
||||
addListener(ListChangeListener { op(it) })
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user