This commit is contained in:
huangyuhui 2018-01-01 16:21:54 +08:00
parent d2529cfc23
commit d8be9abcc5
59 changed files with 184 additions and 176 deletions

View File

@ -17,14 +17,24 @@
*/
package org.jackhuang.hmcl.auth
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile
import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.UUIDTypeAdapter
@Immutable
data class AuthInfo(
data class AuthInfo @JvmOverloads constructor(
val username: String,
val userId: String,
val authToken: String,
val userType: UserType = UserType.LEGACY,
val userProperties: String = "{}",
val userPropertyMap: String = "{}"
)
) {
constructor(profile: GameProfile,
authToken: String,
userType: UserType = UserType.LEGACY,
userProperties: String = "{}",
userPropertyMap: String = "{}")
: this(profile.name!!, UUIDTypeAdapter.fromUUID(profile.id!!), authToken, userType, userProperties, userPropertyMap) {
}
}

View File

@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil
data class AuthenticationRequest(
data class AuthenticationRequest @JvmOverloads constructor(
val username: String,
val password: String,
val clientToken: String,

View File

@ -21,7 +21,7 @@ import com.google.gson.*
import java.lang.reflect.Type
import java.util.*
data class GameProfile(
data class GameProfile @JvmOverloads constructor(
val id: UUID? = null,
val name: String? = null,
val properties: PropertyMap = PropertyMap(),
@ -40,7 +40,7 @@ data class GameProfile(
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 id = if (json.has("id")) context.deserialize(json.get("id"), UUID::class.java) else null
val name = if (json.has("name")) json.getAsJsonPrimitive("name").asString else null
return GameProfile(id, name)
}

View File

@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil
data class RefreshRequest(
data class RefreshRequest @JvmOverloads constructor(
val clientToken: String,
val accessToken: String,
val selectedProfile: GameProfile? = null,

View File

@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil
class Response (
class Response @JvmOverloads constructor(
val accessToken: String? = null,
val clientToken: String? = null,
val selectedProfile: GameProfile? = null,

View File

@ -20,7 +20,7 @@ package org.jackhuang.hmcl.auth.yggdrasil
import com.google.gson.JsonParseException
import org.jackhuang.hmcl.util.Validation
class User (
class User @JvmOverloads constructor(
val id: String = "",
val properties: PropertyMap? = null
) : Validation {

View File

@ -57,8 +57,7 @@ class YggdrasilAccount private constructor(override val username: String): Accou
override fun logIn(proxy: Proxy): AuthInfo {
if (canPlayOnline)
return AuthInfo(
username = selectedProfile!!.name!!,
userId = UUIDTypeAdapter.fromUUID(selectedProfile!!.id!!),
profile = selectedProfile!!,
authToken = accessToken!!,
userType = userType,
userProperties = GSON.toJson(userProperties)
@ -72,8 +71,7 @@ class YggdrasilAccount private constructor(override val username: String): Accou
throw UnsupportedOperationException("Do not support multi-available-profiles account yet.")
} else {
return AuthInfo(
username = selectedProfile!!.name!!,
userId = UUIDTypeAdapter.fromUUID(selectedProfile!!.id!!),
profile = selectedProfile!!,
authToken = accessToken!!,
userType = userType,
userProperties = GSON.toJson(userProperties)
@ -149,7 +147,7 @@ class YggdrasilAccount private constructor(override val username: String): Accou
if (!profile.properties.isEmpty())
result[STORAGE_KEY_PROFILE_PROPERTIES] = profile.properties.toList()
}
if (accessToken != null && accessToken!!.isNotBlank())
if (!accessToken.isNullOrBlank())
result[STORAGE_KEY_ACCESS_TOKEN] = accessToken!!
return result
@ -163,7 +161,7 @@ class YggdrasilAccount private constructor(override val username: String): Accou
val response = GSON.fromJson<Response>(jsonResult) ?: return null
if (response.error?.isNotBlank() ?: false) {
if (!response.error.isNullOrBlank()) {
LOG.severe("Failed to log in, the auth server returned an error: " + response.error + ", message: " + response.errorMessage + ", cause: " + response.cause)
if (response.errorMessage != null)
if (response.errorMessage.contains("Invalid credentials"))
@ -216,10 +214,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
fun randomToken() = UUIDTypeAdapter.fromUUID(UUID.randomUUID())
override fun fromUsername(username: String, password: String): YggdrasilAccount {
val account = YggdrasilAccount(username)
account.password = password
return account
override fun fromUsername(username: String, password: String) = YggdrasilAccount(username).apply {
this.password = password
}
override fun fromStorage(storage: Map<Any, Any>): YggdrasilAccount {

View File

@ -17,16 +17,10 @@
*/
package org.jackhuang.hmcl.download
import org.jackhuang.hmcl.game.GameRepository
import java.net.Proxy
abstract class AbstractDependencyManager
: DependencyManager {
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)
}

View File

@ -25,7 +25,7 @@ import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList
/**
* @see {@link http://bmclapi2.bangbang93.com}
*/
object BMCLAPIDownloadProvider : DownloadProvider() {
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/"

View File

@ -34,7 +34,7 @@ import java.net.Proxy
/**
* This class has no state.
*/
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, override val proxy: Proxy = Proxy.NO_PROXY)
class DefaultDependencyManager @JvmOverloads constructor(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, override val proxy: Proxy = Proxy.NO_PROXY)
: AbstractDependencyManager() {
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)
@ -48,17 +48,19 @@ class DefaultDependencyManager(override val repository: DefaultGameRepository, o
return ParallelTask(*tasks)
}
override fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task {
if (libraryId == "forge")
return ForgeInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
else if (libraryId == "liteloader")
return LiteLoaderInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
else if (libraryId == "optifine")
return OptiFineInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
else
throw IllegalArgumentException("Library id $libraryId is unrecognized.")
}
override fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task =
when (libraryId) {
"forge" ->
ForgeInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
"liteloader" ->
LiteLoaderInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
"optifine" ->
OptiFineInstallTask(this, gameVersion, version, libraryVersion)
.then { VersionJSONSaveTask(repository, it["version"]) }
else ->
throw IllegalArgumentException("Library id $libraryId is unrecognized.")
}
}

View File

@ -20,12 +20,12 @@ package org.jackhuang.hmcl.download
/**
* The service provider that provides Minecraft online file downloads.
*/
abstract class DownloadProvider {
abstract val libraryBaseURL: String
abstract val versionListURL: String
abstract val versionBaseURL: String
abstract val assetIndexBaseURL: String
abstract val assetBaseURL: String
interface DownloadProvider {
val libraryBaseURL: String
val versionListURL: String
val versionBaseURL: String
val assetIndexBaseURL: String
val assetBaseURL: String
/**
* Inject into original URL provided by Mojang and Forge.
@ -36,12 +36,12 @@ abstract class DownloadProvider {
* @param baseURL original URL provided by Mojang and Forge.
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
*/
abstract fun injectURL(baseURL: String): String
fun injectURL(baseURL: String): String
/**
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
* @return the version list
*/
abstract fun getVersionListById(id: String): VersionList<*>
fun getVersionListById(id: String): VersionList<*>
}

View File

@ -26,10 +26,10 @@ import java.util.*
/**
* @see {@link http://wiki.vg}
*/
object MojangDownloadProvider : DownloadProvider() {
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 versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/"
override val assetIndexBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/indexes/"
override val assetBaseURL: String = "http://resources.download.minecraft.net/"

View File

@ -23,7 +23,7 @@ import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.Validation
@Immutable
internal class ForgeVersion (
internal class ForgeVersion @JvmOverloads constructor(
val branch: String? = null,
val mcversion: String? = null,
val jobver: String? = null,
@ -40,7 +40,7 @@ internal class ForgeVersion (
}
@Immutable
internal class ForgeVersionRoot (
internal class ForgeVersionRoot @JvmOverloads constructor(
val artifact: String? = null,
val webpath: String? = null,
val adfly: String? = null,
@ -58,7 +58,7 @@ internal class ForgeVersionRoot (
}
@Immutable
internal data class Install (
internal data class Install @JvmOverloads constructor(
val profileName: String? = null,
val target: String? = null,
val path: String? = null,
@ -71,7 +71,7 @@ internal data class Install (
)
@Immutable
internal data class InstallProfile (
internal data class InstallProfile @JvmOverloads constructor(
@SerializedName("install")
val install: Install? = null,
@SerializedName("versionInfo")

View File

@ -42,7 +42,7 @@ object ForgeVersionList : VersionList<Unit>() {
for ((x, versions) in root.mcversion!!.entries) {
val gameVersion = x.asVersion() ?: continue
for (v in versions) {
val version = root.number!!.get(v) ?: continue
val version = root.number!![v] ?: continue
var jar: String? = null
for (file in version.files!!)
if (file.getOrNull(1) == "installer") {

View File

@ -102,7 +102,7 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
val index = GSON.fromJson<AssetIndex>(assetIndexFile.readText())
val res = LinkedList<Pair<File, AssetObject>>()
var progress = 0
index?.objects?.entries?.forEach { (_, assetObject) ->
index?.objects?.values?.forEach { assetObject ->
res += Pair(dependencyManager.repository.getAssetObject(version.id, assetIndexInfo.id, assetObject), assetObject)
updateProgress(++progress, index.objects.size)
}
@ -119,7 +119,7 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
* @param version the **resolved** version
*/
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, version: Version) : Task() {
private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
override val dependents = listOf(refreshTask)
override val dependencies = LinkedList<Task>()

View File

@ -33,7 +33,7 @@ internal class GameRemoteLatestVersions(
)
@Immutable
internal class GameRemoteVersion(
internal class GameRemoteVersion @JvmOverloads constructor(
@SerializedName("id")
val gameVersion: String = "",
@SerializedName("time")

View File

@ -44,7 +44,7 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
init {
if (!liteLoaderVersionList.loaded)
dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider).then {
dependents += liteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider).then {
remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found")
null
}

View File

@ -22,7 +22,7 @@ import org.jackhuang.hmcl.game.Library
import org.jackhuang.hmcl.util.Immutable
@Immutable
internal data class LiteLoaderVersionsMeta (
internal data class LiteLoaderVersionsMeta @JvmOverloads constructor(
@SerializedName("description")
val description: String = "",
@SerializedName("authors")
@ -32,7 +32,7 @@ internal data class LiteLoaderVersionsMeta (
)
@Immutable
internal data class LiteLoaderRepository (
internal data class LiteLoaderRepository @JvmOverloads constructor(
@SerializedName("stream")
val stream: String = "",
@SerializedName("type")
@ -44,7 +44,7 @@ internal data class LiteLoaderRepository (
)
@Immutable
internal class LiteLoaderVersion (
internal class LiteLoaderVersion @JvmOverloads constructor(
@SerializedName("tweakClass")
val tweakClass: String = "",
@SerializedName("file")
@ -62,7 +62,7 @@ internal class LiteLoaderVersion (
)
@Immutable
internal class LiteLoaderBranch (
internal class LiteLoaderBranch @JvmOverloads constructor(
@SerializedName("libraries")
val libraries: Collection<Library> = emptyList(),
@SerializedName("com.mumfrey:liteloader")
@ -70,7 +70,7 @@ internal class LiteLoaderBranch (
)
@Immutable
internal class LiteLoaderGameVersions (
internal class LiteLoaderGameVersions @JvmOverloads constructor(
@SerializedName("repo")
val repo: LiteLoaderRepository? = null,
@SerializedName("artefacts")
@ -80,7 +80,7 @@ internal class LiteLoaderGameVersions (
)
@Immutable
internal class LiteLoaderVersionsRoot (
internal class LiteLoaderVersionsRoot @JvmOverloads constructor(
@SerializedName("versions")
val versions: Map<String, LiteLoaderGameVersions> = emptyMap(),
@SerializedName("meta")

View File

@ -18,4 +18,4 @@
package org.jackhuang.hmcl.download.optifine
object Opt
object OptiFineDownloadFormatter

View File

@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download.optifine
import com.google.gson.annotations.SerializedName
data class OptiFineVersion (
data class OptiFineVersion @JvmOverloads constructor(
@SerializedName("dl")
val downloadLink: String? = null,
@SerializedName("ver")

View File

@ -48,15 +48,15 @@ object OptiFineVersionList : VersionList<Unit>() {
val doc = db.parse(ByteArrayInputStream(html.toByteArray()))
val r = doc.documentElement
val tables = r.getElementsByTagName("table")
for (i in 0..tables.length - 1) {
for (i in 0 until tables.length) {
val e = tables.item(i) as Element
if ("downloadTable" == e.getAttribute("class")) {
val tr = e.getElementsByTagName("tr")
for (k in 0..tr.length - 1) {
for (k in 0 until tr.length) {
val downloadLine = (tr.item(k) as Element).getElementsByTagName("td")
var url: String? = null
var version: String? = null
for (j in 0..downloadLine.length - 1) {
for (j in 0 until downloadLine.length) {
val td = downloadLine.item(j) as Element
if (td.getAttribute("class")?.startsWith("downloadLineMirror") ?: false)
url = (td.getElementsByTagName("a").item(0) as Element).getAttribute("href")

View File

@ -21,7 +21,7 @@ import org.jackhuang.hmcl.task.Scheduler
import java.util.*
class EventBus {
val events = HashMap<Class<*>, EventManager<*>>()
private val events = HashMap<Class<*>, EventManager<*>>()
@Suppress("UNCHECKED_CAST")
fun <T : EventObject> channel(classOfT: Class<T>): EventManager<T> {

View File

@ -21,7 +21,7 @@ import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.util.SimpleMultimap
import java.util.*
class EventManager<T : EventObject>(val scheduler: Scheduler = Scheduler.IMMEDIATE) {
class EventManager<T : EventObject> @JvmOverloads constructor(val scheduler: Scheduler = Scheduler.IMMEDIATE) {
private val handlers = SimpleMultimap<EventPriority, (T) -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
private val handlers2 = SimpleMultimap<EventPriority, () -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)

View File

@ -22,20 +22,17 @@ import java.util.*
/**
* This event gets fired when loading versions in a .minecraft folder.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
* *
* @param IMinecraftService .minecraft folder.
* *
* @author huangyuhui
* <br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
*
* @param source [org.jackhuang.hmcl.game.GameRepository]
*/
class RefreshingVersionsEvent(source: Any) : EventObject(source)
/**
* This event gets fired when all the versions in .minecraft folder are loaded.
* <br>
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.game.GameRepository]
* @author huangyuhui
*/
@ -43,9 +40,9 @@ class RefreshedVersionsEvent(source: Any) : EventObject(source)
/**
* This event gets fired when a minecraft version has been loaded.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
* <br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.game.GameRepository]
* @param version the version id.
* @author huangyuhui
*/
@ -54,29 +51,29 @@ class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(sour
/**
* This event gets fired when a JavaProcess exited abnormally and the exit code is not zero.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
* @param JavaProcess The process that exited abnormally.
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.launch.ExitWaiter]
* @param value The process that exited abnormally.
* @author huangyuhui
*/
class JavaProcessExitedAbnormallyEvent(source: Any, val value: ManagedProcess) : EventObject(source)
class ProcessExitedAbnormallyEvent(source: Any, val value: ManagedProcess) : EventObject(source)
/**
* This event gets fired when minecraft process exited successfully and the exit code is 0.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
* @param JavaProcess minecraft process
* <br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.launch.ExitWaiter]
* @param value minecraft process
* @author huangyuhui
*/
class JavaProcessStoppedEvent(source: Any, val value: ManagedProcess) : EventObject(source)
class ProcessStoppedEvent(source: Any, val value: ManagedProcess) : EventObject(source)
/**
* This event gets fired when we launch the JVM and it got crashed.
* <br></br>
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
* @param source [org.jackhuang.hmcl.util.sys.JavaProcessMonitor]
* @param JavaProcess the crashed process.
* <br>
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
* @param source [org.jackhuang.hmcl.launch.ExitWaiter]
* @param value the crashed process.
* @author huangyuhui
*/
class JVMLaunchFailedEvent(source: Any, val value: ManagedProcess) : EventObject(source)

View File

@ -17,5 +17,4 @@
*/
package org.jackhuang.hmcl.event
open class FailedEvent<T>(source: Any, val failedTime: Int, var newResult: T) : Event(source) {
}
open class FailedEvent<T>(source: Any, val failedTime: Int, var newResult: T) : Event(source)

View File

@ -20,7 +20,7 @@ package org.jackhuang.hmcl.game
import com.google.gson.annotations.SerializedName
import java.util.*
class AssetIndex(
class AssetIndex @JvmOverloads constructor(
@SerializedName("virtual")
val virtual: Boolean = false,
objects: Map<String, AssetObject> = emptyMap()

View File

@ -20,7 +20,7 @@ package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.util.Immutable
@Immutable
class AssetIndexInfo(
class AssetIndexInfo @JvmOverloads constructor(
url: String = "",
sha1: String? = null,
size: Int = 0,

View File

@ -19,7 +19,7 @@ package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.util.Validation
data class AssetObject(
data class AssetObject @JvmOverloads constructor(
val hash: String = "",
val size: Long = 0
): Validation {

View File

@ -21,7 +21,7 @@ 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 {
class CircleDependencyException : GameException {
constructor() : super() {}
constructor(message: String) : super(message) {}
constructor(message: String, cause: Throwable) : super(message, cause) {}

View File

@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.ignoreThrowable
import java.util.regex.Pattern
@Immutable
data class CompatibilityRule(
data class CompatibilityRule @JvmOverloads constructor(
val action: Action = CompatibilityRule.Action.ALLOW,
val os: OSRestriction? = null,
val features: Map<String, Boolean>? = null
@ -33,8 +33,8 @@ data class CompatibilityRule(
fun getAppliedAction(supportedFeatures: Map<String, Boolean>): Action? {
if (os != null && !os.allow()) return null
if (features != null) {
features.entries.forEach {
if (supportedFeatures[it.key] != it.value)
features.entries.forEach { (key, value) ->
if (supportedFeatures[key] != value)
return null
}
}

View File

@ -57,12 +57,16 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
* {@inheritsDoc}
* @return something like ".minecraft/versions/<version name>/<version name>-natives"
*/
override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives")
override fun getVersionRoot(id: String) = File(baseDirectory, "versions/$id")
open fun getVersionJson(id: String) = File(getVersionRoot(id), "$id.json")
override fun getNativeDirectory(id: String) = getVersionRoot(id).resolve("$id-natives")
override fun getVersionRoot(id: String) = baseDirectory.resolve("versions/$id")
open fun getVersionJson(id: String) = getVersionRoot(id).resolve("$id.json")
@Throws(IOException::class, JsonSyntaxException::class)
open fun readVersionJson(id: String): Version? = readVersionJson(getVersionJson(id))
@Throws(IOException::class, JsonSyntaxException::class, VersionNotFoundException::class)
@Throws(IOException::class, JsonSyntaxException::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)
@ -87,6 +91,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
return false
}
}
open fun removeVersionFromDisk(name: String): Boolean {
val file = getVersionRoot(name)
if (!file.exists())
@ -153,7 +158,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
override fun getActualAssetDirectory(version: String, assetId: String): File {
try {
return reconstructAssets(version, assetId)
} catch (e: IOException) {
} catch (e: Exception) {
LOG.log(Level.SEVERE, "Unable to reconstruct asset directory", e)
return getAssetDirectory(version, assetId)
}

View File

@ -23,7 +23,7 @@ import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.Validation
@Immutable
open class DownloadInfo(
open class DownloadInfo @JvmOverloads constructor(
@SerializedName("url")
val url: String = "",
@SerializedName("sha1")
@ -38,7 +38,7 @@ open class DownloadInfo(
}
@Immutable
open class IdDownloadInfo(
open class IdDownloadInfo @JvmOverloads constructor(
url: String = "",
sha1: String? = null,
size: Int = 0,

View File

@ -20,13 +20,13 @@ package org.jackhuang.hmcl.game
import com.google.gson.annotations.SerializedName
import java.util.*
class ExtractRules(exclude: List<String> = emptyList()) {
class ExtractRules @JvmOverloads constructor(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()
exclude.none { path.startsWith(it) }
}

View File

@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.game
class GameException : Exception {
open class GameException : Exception {
constructor() : super() {}
constructor(message: String) : super(message) {}
constructor(message: String, cause: Throwable) : super(message, cause) {}

View File

@ -32,7 +32,7 @@ private fun lessThan32(b: ByteArray, startIndex: Int): Int {
}
fun matchArray(a: ByteArray, b: ByteArray): Int {
for (i in 0..a.size - b.size - 1) {
for (i in 0 until a.size - b.size) {
var j = 1
for (k in b.indices) {
if (b[k] == a[i + k])

View File

@ -23,7 +23,7 @@ import java.util.*
import kotlin.collections.HashMap
@Immutable
class LibrariesDownloadInfo(
class LibrariesDownloadInfo @JvmOverloads constructor(
@SerializedName("artifact")
val artifact: LibraryDownloadInfo? = null,
classifier_: Map<String, LibraryDownloadInfo>? = null

View File

@ -24,11 +24,9 @@ import java.lang.reflect.Type
/**
* A class that describes a Minecraft dependency.
*
* @see LibraryDeserializer
*/
@Immutable
open class Library(
open class Library @JvmOverloads constructor(
val groupId: String,
val artifactId: String,
val version: String,
@ -100,6 +98,7 @@ open class Library(
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()

View File

@ -21,7 +21,7 @@ import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.util.Immutable
@Immutable
class LibraryDownloadInfo(
class LibraryDownloadInfo @JvmOverloads constructor(
url: String = "",
sha1: String? = null,
size: Int = 0,

View File

@ -22,9 +22,9 @@ import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.Validation
@Immutable
class LoggingInfo(
class LoggingInfo @JvmOverloads constructor(
@SerializedName("file")
val file: IdDownloadInfo = IdDownloadInfo(""),
val file: IdDownloadInfo = IdDownloadInfo(),
@SerializedName("argument")
val argument: String = "",

View File

@ -20,7 +20,7 @@ package org.jackhuang.hmcl.game
import java.util.*
import java.util.regex.Pattern
class StringArgument(var argument: String) : Argument {
class StringArgument(val argument: String) : Argument {
override fun toString(keys: Map<String, String>, features: Map<String, Boolean>): List<String> {
var res = argument

View File

@ -23,7 +23,7 @@ import org.jackhuang.hmcl.util.*
import java.util.*
@Immutable
open class Version(
open class Version @JvmOverloads constructor(
@SerializedName("minecraftArguments")
val minecraftArguments: String? = null,
@SerializedName("arguments")
@ -88,7 +88,7 @@ open class Version(
val actualAssetIndex: AssetIndexInfo
get() {
val id = assets ?: "legacy"
return assetIndex ?: AssetIndexInfo(id = id, url = "$DEFAULT_VERSION_DOWNLOAD_URL$id.json")
return assetIndex ?: AssetIndexInfo(id = id, url = "$DEFAULT_INDEX_URL$id.json")
}
fun appliesToCurrentEnvironment() = CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules)
@ -134,8 +134,9 @@ open class Version(
arguments = Arguments.mergeArguments(parent.arguments, this.arguments),
releaseTime = this.releaseTime,
inheritsFrom = null,
compatibilityRules = merge(this.compatibilityRules, parent.compatibilityRules),
minecraftArguments = this.minecraftArguments ?: parent.minecraftArguments,
minimumLauncherVersion = parent.minimumLauncherVersion
minimumLauncherVersion = maxOf(minimumLauncherVersion, parent.minimumLauncherVersion)
)
}

View File

@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.game
class VersionNotFoundException : Exception {
class VersionNotFoundException : GameException {
constructor() : super() {}
constructor(message: String) : super(message) {}
constructor(message: String, cause: Throwable) : super(message, cause) {}

View File

@ -31,7 +31,7 @@ import kotlin.concurrent.thread
* @param account The user account
* @param options The launching configuration
*/
open class DefaultLauncher(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
open class DefaultLauncher @JvmOverloads constructor(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
: Launcher(repository, versionId, account, options, listener, isDaemon) {
protected val native: File by lazy { repository.getNativeDirectory(version.id) }
@ -143,7 +143,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
val features = getFeatures()
res.addAll(Arguments.parseArguments(version.arguments?.game ?: defaultGameArguments, configuration, features))
res.addAll(Arguments.parseStringArguments(version.minecraftArguments?.tokenize()?.toList() ?: emptyList(), configuration))
res.addAll(Arguments.parseStringArguments(version.minecraftArguments?.tokenize() ?: emptyList(), configuration))
// Optional Minecraft arguments
if (options.height != null && options.height != 0 && options.width != null && options.width != 0) {

View File

@ -19,8 +19,8 @@ package org.jackhuang.hmcl.launch
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.JVMLaunchFailedEvent
import org.jackhuang.hmcl.event.JavaProcessExitedAbnormallyEvent
import org.jackhuang.hmcl.event.JavaProcessStoppedEvent
import org.jackhuang.hmcl.event.ProcessExitedAbnormallyEvent
import org.jackhuang.hmcl.event.ProcessStoppedEvent
import org.jackhuang.hmcl.util.ManagedProcess
import org.jackhuang.hmcl.util.containsOne
import org.jackhuang.hmcl.util.guessLogLineError
@ -34,13 +34,13 @@ internal class ExitWaiter(val process: ManagedProcess, val joins: Collection<Thr
try {
val exitCode = process.process.waitFor()
joins.forEach { it.join() }
joins.forEach(Thread::join)
val errorLines = process.lines.filter(::guessLogLineError)
val exitType: ProcessListener.ExitType
// LaunchWrapper will catch the exception logged and will exit normally.
if (exitCode != 0 || errorLines.containsOne("Unable to launch")) {
EVENT_BUS.fireEvent(JavaProcessExitedAbnormallyEvent(this, process))
EVENT_BUS.fireEvent(ProcessExitedAbnormallyEvent(this, process))
exitType = ProcessListener.ExitType.APPLICATION_ERROR
} else if (exitCode != 0 && errorLines.containsOne(
"Could not create the Java Virtual Machine.",
@ -52,7 +52,7 @@ internal class ExitWaiter(val process: ManagedProcess, val joins: Collection<Thr
} else
exitType = ProcessListener.ExitType.NORMAL
EVENT_BUS.fireEvent(JavaProcessStoppedEvent(this, process))
EVENT_BUS.fireEvent(ProcessStoppedEvent(this, process))
watcher(exitCode, exitType)
} catch (e: InterruptedException) {

View File

@ -24,7 +24,7 @@ import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.util.ManagedProcess
import java.io.File
abstract class Launcher(
abstract class Launcher @JvmOverloads constructor(
protected val repository: GameRepository,
protected val versionId: String,
protected val account: AuthInfo,

View File

@ -124,8 +124,8 @@ internal class Log4jHandler(private val callback: (String, Log4jLevel) -> Unit)
}
}
override fun characters(ch: CharArray?, start: Int, length: Int) {
val line = String(ch!!, start, length)
override fun characters(ch: CharArray, start: Int, length: Int) {
val line = String(ch, start, length)
if (line.trim { it <= ' ' }.isEmpty()) return
if (readingMessage)
message!!.append(line).append(OS.LINE_SEPARATOR)

View File

@ -58,7 +58,7 @@ class CurseForgeModpackManifest @JvmOverloads constructor(
}
}
class CurseForgeModpackManifestMinecraft (
class CurseForgeModpackManifestMinecraft @JvmOverloads constructor(
@SerializedName("version")
val gameVersion: String = "",
@SerializedName("modLoaders")
@ -69,7 +69,7 @@ class CurseForgeModpackManifestMinecraft (
}
}
class CurseForgeModpackManifestModLoader (
class CurseForgeModpackManifestModLoader @JvmOverloads constructor(
@SerializedName("id")
val id: String = "",
@SerializedName("primary")
@ -80,7 +80,7 @@ class CurseForgeModpackManifestModLoader (
}
}
class CurseForgeModpackManifestFile (
class CurseForgeModpackManifestFile @JvmOverloads constructor(
@SerializedName("projectID")
val projectID: Int = 0,
@SerializedName("fileID")
@ -185,10 +185,10 @@ class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, vers
val manifestFile = repository.getVersionRoot(version).resolve("manifest.json")
if (!manifestFile.exists()) manifest = null
else {
manifest = GSON.fromJson<CurseForgeModpackManifest>(repository.getVersionRoot(version).resolve("manifest.json").readText())!!
manifest = GSON.fromJson<CurseForgeModpackManifest>(manifestFile.readText())!!
// Because in China, the CurseForge is too difficult to visit.
// So caching the file name is necessary.
// Because in China, CurseForge is too difficult to visit,
// caching the file name is necessary.
for (f in manifest!!.files) {
if (f.fileName.isBlank())
dependents += task { f.fileName = f.url.detectFileName(proxy) }

View File

@ -22,7 +22,7 @@ import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import java.io.File
class ModInfo (
class ModInfo @JvmOverloads constructor(
var file: File,
val name: String,
val description: String = "",

View File

@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.AutoTypingMap
*
* @param pred the task that runs before [succ]
* @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred].
* @param reliant true if this task chain will be broken when task [pred] fails.
* @param reliesOnDependents true if this task chain will be broken when task [pred] fails.
*/
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliesOnDependents: Boolean) : Task() {
override val hidden: Boolean = true

View File

@ -46,7 +46,7 @@ class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, v
/**
* Once downloading fails, this event will be fired to gain the substitute URL.
*/
var onFailed = EventManager<FailedEvent<URL>>()
val onFailed = EventManager<FailedEvent<URL>>()
private var rFile: RandomAccessFile? = null
private var stream: InputStream? = null
@ -81,7 +81,7 @@ class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, v
try {
updateProgress(0.0)
val conn = url.createConnection(proxy)
val conn = currentURL.createConnection(proxy)
conn.connect()
if (conn.responseCode / 100 != 2)

View File

@ -56,9 +56,9 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
val len = input.read(buf)
if (len == -1)
break
read += len
baos.write(buf, 0, len)
read += len
updateProgress(read, size)
if (Thread.currentThread().isInterrupted)

View File

@ -57,11 +57,13 @@ interface Scheduler {
val e = wrapper.get()
if (e != null) throw ExecutionException(e)
}
override fun get() {
latch.await()
val e = wrapper.get()
if (e != null) throw ExecutionException(e)
}
override fun isDone() = latch.count == 0L
override fun isCancelled() = false
override fun cancel(mayInterruptIfRunning: Boolean) = false
@ -117,19 +119,25 @@ interface Scheduler {
* A scheduler that always create new threads to execute tasks.
* For tasks that do not do heavy operations.
*/
val NEW_THREAD: Scheduler = SchedulerExecutorService(CACHED_EXECUTOR)
val NEW_THREAD: Scheduler by lazy {
SchedulerExecutorService(CACHED_EXECUTOR)
}
/**
* A scheduler that exclusively executes tasks that do I/O operations.
* The number tasks that do I/O operations in the meantime cannot be larger then 6.
*/
val IO: Scheduler = SchedulerExecutorService(IO_EXECUTOR)
val IO: Scheduler by lazy {
SchedulerExecutorService(IO_EXECUTOR)
}
/**
* A scheduler that exclusively executes tasks that do computations.
* The best way to do computations is an event queue.
*/
val COMPUTATION: Scheduler = SchedulerExecutorService(SINGLE_EXECUTOR)
val COMPUTATION: Scheduler by lazy {
SchedulerExecutorService(SINGLE_EXECUTOR)
}
/**
* The default scheduler for tasks to be executed.

View File

@ -35,6 +35,7 @@ class TaskExecutor(private val task: Task) {
val totTask = AtomicInteger(0)
val variables = AutoTypingMap<String>(mutableMapOf())
var lastException: Exception? = null
private set
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
/**
@ -151,7 +152,7 @@ class TaskExecutor(private val task: Task) {
return flag
}
private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Callable<Unit> {
private inner class Invoker(private val task: Task, private val latch: CountDownLatch, private val boolean: AtomicBoolean): Callable<Unit> {
override fun call() {
try {
Thread.currentThread().name = task.title

View File

@ -28,7 +28,7 @@ class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) {
operator fun <V> get(key: K): V = impl[key] as V
operator fun set(key: K, value: Any?) {
if (value != null)
impl.set(key, value)
impl[key] = value
}
val values get() = impl.values
val keys get() = impl.keys

View File

@ -151,7 +151,6 @@ fun File.uncompressTo(dest: File, subDirectory: String = "", callback: ((String)
/**
* Read the text content of a file in zip.
*
* @param f the zip file
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @throws IOException if the file is not a valid zip file.
* @return the content of given file.
@ -167,7 +166,6 @@ fun File.readTextZipEntry(name: String): String {
/**
* Read the text content of a file in zip.
*
* @param f the zip file
* @param location the location of the text in zip file, something like A/B/C/D.txt
* @return the content of given file.
*/

View File

@ -78,16 +78,16 @@ interface Validation {
object ValidationTypeAdapterFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T?>?): TypeAdapter<T?> {
val delgate = gson.getDelegateAdapter(this, type)
val delegate = 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)
delegate.write(out, value)
}
override fun read(reader: JsonReader?): T? {
val value = delgate.read(reader)
val value = delegate.read(reader)
if (value is Validation)
value.validate()
return value
@ -160,7 +160,7 @@ object DateTypeAdapter : JsonSerializer<Date>, JsonDeserializer<Date> {
if (json !is JsonPrimitive) {
throw JsonParseException("The date should be a string value")
} else {
val date = this.deserializeToDate(json.getAsString())
val date = this.deserializeToDate(json.asString)
if (typeOfT === Date::class.java) {
return date
} else {
@ -170,23 +170,23 @@ object DateTypeAdapter : JsonSerializer<Date>, JsonDeserializer<Date> {
}
override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
synchronized(this.enUsFormat) {
synchronized(enUsFormat) {
return JsonPrimitive(this.serializeToString(src))
}
}
fun deserializeToDate(string: String): Date {
synchronized(this.enUsFormat) {
synchronized(enUsFormat) {
try {
return this.enUsFormat.parse(string)
return enUsFormat.parse(string)
} catch (ex1: ParseException) {
try {
return this.iso8601Format.parse(string)
return 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)
return iso8601Format.parse(cleaned)
} catch (e: Exception) {
throw JsonSyntaxException("Invalid date: " + string, e)
}

View File

@ -30,9 +30,7 @@ fun Closeable.closeQuietly() {
fun InputStream.readFully(): ByteArrayOutputStream {
try {
val ans = ByteArrayOutputStream()
copyTo(ans)
return ans
return ByteArrayOutputStream().apply { copyTo(this) }
} finally {
this.closeQuietly()
}

View File

@ -47,14 +47,14 @@ fun initHttps() {
if (INIT_HTTPS)
return
INIT_HTTPS = true
System.setProperty("https.protocols", "SSLv3,TLSv1");
System.setProperty("https.protocols", "SSLv3,TLSv1")
try {
val c = SSLContext.getInstance("SSL");
c.init(null, arrayOf(XTM), SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(c.getSocketFactory());
val c = SSLContext.getInstance("SSL")
c.init(null, arrayOf(XTM), SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(c.socketFactory)
} catch (e: GeneralSecurityException) {
}
HttpsURLConnection.setDefaultHostnameVerifier(HNV);
HttpsURLConnection.setDefaultHostnameVerifier(HNV)
}
var DEFAULT_USER_AGENT: () -> String = { RandomUserAgent.randomUserAgent }
@ -144,7 +144,7 @@ fun URL.detectFileName(proxy: Proxy = Proxy.NO_PROXY): String {
fun HttpURLConnection.detectFileName(): String {
val disposition = getHeaderField("Content-Disposition")
if (disposition == null || disposition.indexOf("filename=") == -1) {
if (disposition == null || !disposition.contains("filename=")) {
val u = url.toString()
return u.substringAfterLast('/')
} else

View File

@ -23,8 +23,8 @@ import java.util.*
* A simple implementation of Multimap.
* Just a combination of map and set.
*/
class SimpleMultimap<K, V>(val maper: () -> MutableMap<K, Collection<V>>, val valuer: () -> MutableCollection<V>) {
private val map = HashMap<K, MutableCollection<V>>()
class SimpleMultimap<K, V>(mapper: () -> MutableMap<K, MutableCollection<V>>, private val valuer: () -> MutableCollection<V>) {
private val map = mapper()
private val valuesImpl: MutableCollection<V> = valuer()
val size = valuesImpl.size
@ -35,7 +35,7 @@ class SimpleMultimap<K, V>(val maper: () -> MutableMap<K, Collection<V>>, val va
val isEmpty: Boolean = size == 0
val isNotEmpty: Boolean = size != 0
fun containsKey(key: K): Boolean = map.containsKey(key)
fun containsKey(key: K): Boolean = map[key]?.isNotEmpty() ?: false
operator fun get(key: K): Collection<V> = map.getOrPut(key, valuer)
fun put(key: K, value: V) {