Javadoc
This commit is contained in:
@@ -19,9 +19,7 @@ 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
|
||||
import java.util.*
|
||||
|
||||
data class GameProfile(
|
||||
val id: UUID? = null,
|
||||
|
||||
@@ -20,13 +20,6 @@ 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>() {
|
||||
|
||||
@@ -21,9 +21,6 @@ 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
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import java.net.Proxy
|
||||
|
||||
abstract class AbstractDependencyManager(repository: GameRepository, proxy: Proxy)
|
||||
: DependencyManager(repository, proxy) {
|
||||
abstract class AbstractDependencyManager
|
||||
: DependencyManager {
|
||||
abstract val downloadProvider: DownloadProvider
|
||||
|
||||
fun getVersions(id: String, selfVersion: String) =
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* @see {@link http://bmclapi2.bangbang93.com}
|
||||
*/
|
||||
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"
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask
|
||||
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.download.optifine.OptiFineInstallTask
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.ParallelTask
|
||||
@@ -27,8 +34,8 @@ import java.net.Proxy
|
||||
/**
|
||||
* This class has no state.
|
||||
*/
|
||||
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, proxy: Proxy = Proxy.NO_PROXY)
|
||||
: AbstractDependencyManager(repository, proxy) {
|
||||
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, override val proxy: Proxy = Proxy.NO_PROXY)
|
||||
: AbstractDependencyManager() {
|
||||
|
||||
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)
|
||||
|
||||
|
||||
@@ -17,9 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.game.*
|
||||
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.Version
|
||||
import org.jackhuang.hmcl.task.*
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import java.util.*
|
||||
|
||||
class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameBuilder() {
|
||||
|
||||
@@ -22,27 +22,51 @@ import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import java.net.Proxy
|
||||
|
||||
abstract class DependencyManager(open val repository: GameRepository, open val proxy: Proxy) {
|
||||
/**
|
||||
* Do everything that will connect to Internet.
|
||||
* Downloading Minecraft files.
|
||||
*/
|
||||
interface DependencyManager {
|
||||
/**
|
||||
* The relied game repository.
|
||||
*/
|
||||
val repository: GameRepository
|
||||
|
||||
/**
|
||||
* The proxy that all network operations should go through.
|
||||
*/
|
||||
val proxy: Proxy
|
||||
|
||||
/**
|
||||
* Check if the game is complete.
|
||||
* Check libraries, assets, logging files and so on.
|
||||
*
|
||||
* @return
|
||||
* @return the task to check game completion.
|
||||
*/
|
||||
abstract fun checkGameCompletionAsync(version: Version): Task
|
||||
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
|
||||
fun gameBuilder(): GameBuilder
|
||||
|
||||
abstract fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task
|
||||
/**
|
||||
* Install a library to a version.
|
||||
* **Note**: Installing a library may change the version.json.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that the library relies on.
|
||||
* @param version the version.json.
|
||||
* @param libraryId the type of being installed library. i.e. "forge", "liteloader", "optifine"
|
||||
* @param libraryVersion the version of being installed library.
|
||||
* @return the task to install the specific library.
|
||||
*/
|
||||
fun installLibraryAsync(gameVersion: String, 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<*>
|
||||
fun getVersionList(id: String): VersionList<*>
|
||||
}
|
||||
@@ -17,14 +17,31 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Inject into original URL provided by Mojang and Forge.
|
||||
*
|
||||
* Since there are many provided URLs that are written in JSONs and are unmodifiable,
|
||||
* this method provides a way to change them.
|
||||
*
|
||||
* @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
|
||||
|
||||
/**
|
||||
* 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<*>
|
||||
}
|
||||
@@ -19,12 +19,20 @@ package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
|
||||
|
||||
/**
|
||||
* The builder which provide a task to build Minecraft environment.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
abstract class GameBuilder {
|
||||
var name: String = ""
|
||||
protected var gameVersion: String = ""
|
||||
protected var toolVersions = HashMap<String, String>()
|
||||
|
||||
/**
|
||||
* The new game version name, for .minecraft/<version name>.
|
||||
* @param name the name of new game version.
|
||||
*/
|
||||
fun name(name: String): GameBuilder {
|
||||
this.name = name
|
||||
return this
|
||||
@@ -35,10 +43,17 @@ abstract class GameBuilder {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the core library id. i.e. "forge", "liteloader", "optifine"
|
||||
* @param version the version of the core library. For documents, you can first try [VersionList.versions]
|
||||
*/
|
||||
fun version(id: String, version: String): GameBuilder {
|
||||
toolVersions[id] = version
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the task that can build thw whole Minecraft environment
|
||||
*/
|
||||
abstract fun buildAsync(): Task
|
||||
}
|
||||
@@ -17,8 +17,15 @@
|
||||
*/
|
||||
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.OptiFineVersionList
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @see {@link http://wiki.vg}
|
||||
*/
|
||||
object MojangDownloadProvider : DownloadProvider() {
|
||||
override val libraryBaseURL: String = "https://libraries.minecraft.net/"
|
||||
override val versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/"
|
||||
@@ -37,15 +44,6 @@ object MojangDownloadProvider : DownloadProvider() {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -19,14 +19,18 @@ package org.jackhuang.hmcl.download
|
||||
|
||||
import org.jackhuang.hmcl.util.VersionNumber
|
||||
import java.util.*
|
||||
import kotlin.Comparator
|
||||
|
||||
/**
|
||||
* The remote version.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
* @param tag some necessary information for Installer Task.
|
||||
*/
|
||||
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>> {
|
||||
@@ -39,6 +43,7 @@ data class RemoteVersion<T> (
|
||||
}
|
||||
|
||||
override fun compareTo(other: RemoteVersion<T>): Int {
|
||||
// newer versions are smaller than older versions
|
||||
return -selfVersion.compareTo(other.selfVersion)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,20 +22,37 @@ import org.jackhuang.hmcl.util.SimpleMultimap
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* The remote version list.
|
||||
* @param T The type of RemoteVersion<T>, the type of tags.
|
||||
*/
|
||||
abstract class VersionList<T> {
|
||||
@JvmField
|
||||
/**
|
||||
* the remote version list.
|
||||
* key: game version.
|
||||
* values: corresponding remote versions.
|
||||
*/
|
||||
protected val versions = SimpleMultimap<String, RemoteVersion<T>>(::HashMap, ::TreeSet)
|
||||
|
||||
/**
|
||||
* True if the version list has been loaded.
|
||||
*/
|
||||
val loaded = versions.isNotEmpty
|
||||
|
||||
/**
|
||||
* @param downloadProvider DownloadProvider
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
abstract fun refreshAsync(downloadProvider: DownloadProvider): Task
|
||||
|
||||
protected open fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> {
|
||||
private fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> {
|
||||
val ans = versions[gameVersion]
|
||||
return if (ans.isEmpty()) versions.values else ans
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote versions that specifics Minecraft version.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @return the collection of specific remote versions
|
||||
*/
|
||||
@@ -43,6 +60,13 @@ abstract class VersionList<T> {
|
||||
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific remote version.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that remote versions belong to
|
||||
* @param remoteVersion the version of the remote version.
|
||||
* @return the specific remote version, null if it is not found.
|
||||
*/
|
||||
fun getVersion(gameVersion: String, remoteVersion: String): RemoteVersion<T>? {
|
||||
var result : RemoteVersion<T>? = null
|
||||
versions[gameVersion].forEach {
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.forge
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
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
|
||||
@@ -30,9 +33,9 @@ import java.io.IOException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val gameVersion: String,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String) : TaskResult<Version>() {
|
||||
private val gameVersion: String,
|
||||
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<*>
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.forge
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
@@ -15,8 +15,11 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.forge
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.*
|
||||
@@ -15,8 +15,11 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.game
|
||||
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.DependencyManager
|
||||
import org.jackhuang.hmcl.game.AssetIndex
|
||||
import org.jackhuang.hmcl.game.AssetObject
|
||||
import org.jackhuang.hmcl.game.DownloadType
|
||||
@@ -30,13 +33,12 @@ 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() {
|
||||
class GameLibrariesTask(private val dependencyManager: AbstractDependencyManager, private val resolvedVersion: Version): Task() {
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
for (library in resolvedVersion.libraries)
|
||||
@@ -49,7 +51,12 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager,
|
||||
|
||||
}
|
||||
|
||||
class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
/**
|
||||
* This task is to download log4j configuration file provided in minecraft.json.
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
|
||||
* @param version the **resolved** version
|
||||
*/
|
||||
class GameLoggingDownloadTask(private val dependencyManager: DependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
val logging = version.logging?.get(DownloadType.CLIENT) ?: return
|
||||
@@ -59,6 +66,11 @@ class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyMa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This task is to download asset index file provided in minecraft.json.
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
|
||||
* @param version the **resolved** version
|
||||
*/
|
||||
class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
@@ -72,6 +84,11 @@ class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependenc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This task is to extract all asset objects described in asset index json.
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
|
||||
* @param version the **resolved** version
|
||||
*/
|
||||
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
|
||||
@@ -100,6 +117,11 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This task is to download all asset objects provided in asset index json.
|
||||
* @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() {
|
||||
private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
|
||||
override val dependents = listOf(refreshTask)
|
||||
@@ -140,6 +162,11 @@ class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyMana
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This task is to save the version json.
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository]
|
||||
* @param version the **resolved** version
|
||||
*/
|
||||
class VersionJSONSaveTask(private val dependencyManager: DefaultDependencyManager, private val version: Version): Task() {
|
||||
override fun execute() {
|
||||
val json = dependencyManager.repository.getVersionJson(version.id).absoluteFile
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.game
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.ReleaseType
|
||||
@@ -15,13 +15,16 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.game
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
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.fromJson
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.liteloader
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
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
|
||||
@@ -27,12 +30,12 @@ import org.jackhuang.hmcl.task.then
|
||||
import org.jackhuang.hmcl.util.merge
|
||||
|
||||
/**
|
||||
* LiteLoader must be installed after Forge.
|
||||
* Note: LiteLoader must be installed after Forge.
|
||||
*/
|
||||
class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val gameVersion: String,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val gameVersion: String,
|
||||
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 = mutableListOf<Task>()
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.liteloader
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
@@ -15,13 +15,16 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.liteloader
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
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.fromJson
|
||||
import org.jackhuang.hmcl.task.GetTask
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
|
||||
object LiteLoaderVersionList : VersionList<LiteLoaderRemoteVersionTag>() {
|
||||
@@ -15,15 +15,17 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.optifine
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
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 {
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.optifine
|
||||
|
||||
|
||||
object Opt
|
||||
@@ -15,8 +15,11 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.optifine
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
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
|
||||
@@ -26,10 +29,13 @@ import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.task.then
|
||||
import org.jackhuang.hmcl.util.merge
|
||||
|
||||
/**
|
||||
* **Note**: OptiFine should be installed in the end.
|
||||
*/
|
||||
class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val gameVersion: String,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val gameVersion: String,
|
||||
private val version: Version,
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val optiFineVersionList = dependencyManager.getVersionList("optifine")
|
||||
lateinit var remote: RemoteVersion<*>
|
||||
override val dependents = mutableListOf<Task>()
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.optifine
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
* 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
|
||||
package org.jackhuang.hmcl.download.optifine
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.VersionList
|
||||
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
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
object OptiFineVersionList : VersionList<Unit>() {
|
||||
private val pattern = Pattern.compile("OptiFine (.*?) ")
|
||||
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.event
|
||||
|
||||
import org.jackhuang.hmcl.util.JavaProcess
|
||||
import java.util.EventObject
|
||||
import org.jackhuang.hmcl.util.ManagedProcess
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This event gets fired when loading versions in a .minecraft folder.
|
||||
@@ -59,7 +59,7 @@ class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(sour
|
||||
* @param JavaProcess The process that exited abnormally.
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class JavaProcessExitedAbnormallyEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||
class JavaProcessExitedAbnormallyEvent(source: Any, val value: ManagedProcess) : EventObject(source)
|
||||
|
||||
/**
|
||||
* This event gets fired when minecraft process exited successfully and the exit code is 0.
|
||||
@@ -69,7 +69,7 @@ class JavaProcessExitedAbnormallyEvent(source: Any, val value: JavaProcess) : Ev
|
||||
* @param JavaProcess minecraft process
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class JavaProcessStoppedEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||
class JavaProcessStoppedEvent(source: Any, val value: ManagedProcess) : EventObject(source)
|
||||
|
||||
/**
|
||||
* This event gets fired when we launch the JVM and it got crashed.
|
||||
@@ -79,4 +79,4 @@ class JavaProcessStoppedEvent(source: Any, val value: JavaProcess) : EventObject
|
||||
* @param JavaProcess the crashed process.
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class JVMLaunchFailedEvent(source: Any, val value: JavaProcess) : EventObject(source)
|
||||
class JVMLaunchFailedEvent(source: Any, val value: ManagedProcess) : EventObject(source)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable
|
||||
import java.net.URL
|
||||
|
||||
@Immutable
|
||||
class AssetIndexInfo(
|
||||
|
||||
@@ -20,6 +20,9 @@ package org.jackhuang.hmcl.game
|
||||
import org.jackhuang.hmcl.util.Immutable
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The Minecraft version for 1.5.x and earlier.
|
||||
*/
|
||||
@Immutable
|
||||
class ClassicVersion : Version(
|
||||
mainClass = "net.minecraft.client.Minecraft",
|
||||
@@ -41,6 +44,9 @@ class ClassicVersion : Version(
|
||||
)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Check if this directory is an old style Minecraft repository.
|
||||
*/
|
||||
fun hasClassicVersion(baseDirectory: File): Boolean {
|
||||
val file = File(baseDirectory, "bin")
|
||||
if (!file.exists()) return false
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import org.jackhuang.hmcl.event.*
|
||||
import org.jackhuang.hmcl.event.EVENT_BUS
|
||||
import org.jackhuang.hmcl.event.LoadedOneVersionEvent
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
@@ -28,6 +31,9 @@ import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
|
||||
/**
|
||||
* An implementation of classic Minecraft game repository.
|
||||
*/
|
||||
open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
|
||||
|
||||
@@ -44,6 +50,11 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||
val id = v.jar ?: v.id
|
||||
return getVersionRoot(id).resolve("$id.jar")
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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")
|
||||
@@ -83,7 +94,6 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||
}
|
||||
|
||||
protected open fun refreshVersionsImpl() {
|
||||
|
||||
versions.clear()
|
||||
|
||||
if (ClassicVersion.hasClassicVersion(baseDirectory)) {
|
||||
@@ -167,7 +177,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||
return assetDir.resolve("objects/${obj.location}")
|
||||
}
|
||||
|
||||
open fun getIndexFile(version: String, assetId: String): File =
|
||||
override fun getIndexFile(version: String, assetId: String): File =
|
||||
getAssetDirectory(version, assetId).resolve("indexes/$assetId.json")
|
||||
|
||||
override fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File =
|
||||
@@ -176,9 +186,8 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||
@Throws(IOException::class, JsonSyntaxException::class)
|
||||
protected open fun reconstructAssets(version: String, assetId: String): File {
|
||||
val assetsDir = getAssetDirectory(version, assetId)
|
||||
val assetVersion = assetId
|
||||
val indexFile: File = getIndexFile(version, assetVersion)
|
||||
val virtualRoot = assetsDir.resolve("virtual").resolve(assetVersion)
|
||||
val indexFile: File = getIndexFile(version, assetId)
|
||||
val virtualRoot = assetsDir.resolve("virtual").resolve(assetId)
|
||||
|
||||
if (!indexFile.isFile) {
|
||||
return assetsDir
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Supports operations on versioning.
|
||||
*
|
||||
* Note that game repository will not do any tasks that connect to Internet.
|
||||
* Note that game repository will not do any tasks that connect to Internet, if do, see [org.jackhuang.hmcl.download.DependencyManager]
|
||||
*/
|
||||
interface GameRepository : VersionProvider {
|
||||
|
||||
/**
|
||||
* Does the version of id exist?
|
||||
* @param id the id of version
|
||||
@@ -126,7 +128,8 @@ interface GameRepository : VersionProvider {
|
||||
* 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]
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
* @throws java.io.IOException if I/O operation fails.
|
||||
* @return the actual asset directory
|
||||
*/
|
||||
@@ -134,24 +137,31 @@ interface GameRepository : VersionProvider {
|
||||
|
||||
/**
|
||||
* Get the asset directory according to the asset id.
|
||||
*
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
* @return the asset directory
|
||||
*/
|
||||
fun getAssetDirectory(version: String, 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]
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
* @param name the asset object name, you can find it in [AssetIndex.objects.keys]
|
||||
* @throws java.io.IOException if I/O operation fails.
|
||||
* @return the file that given asset object refers to
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun getAssetObject(version: String, 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]
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
* @param obj the asset object, you can find it in [AssetIndex.objects]
|
||||
* @return the file that given asset object refers to
|
||||
*/
|
||||
fun getAssetObject(version: String, assetId: String, obj: AssetObject): File
|
||||
@@ -159,17 +169,28 @@ interface GameRepository : VersionProvider {
|
||||
/**
|
||||
* Get asset index that assetId represents
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
* @return the asset index
|
||||
*/
|
||||
fun getAssetIndex(version: String, assetId: String): AssetIndex
|
||||
|
||||
/**
|
||||
* Get the asset_index.json which includes asset objects information.
|
||||
*
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id]
|
||||
*/
|
||||
fun getIndexFile(version: String, assetId: String): File
|
||||
|
||||
/**
|
||||
* Get logging object
|
||||
*
|
||||
* @param assetId the asset id, [AssetIndexInfo.id] [Version.assets]
|
||||
* @param version the id of specific version that is relevant to [assetId]
|
||||
* @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.assets]
|
||||
* @param loggingInfo the logging info
|
||||
* @return the file that loggingInfo refers to
|
||||
*/
|
||||
fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File
|
||||
|
||||
}
|
||||
@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.game
|
||||
|
||||
import org.jackhuang.hmcl.util.closeQuietly
|
||||
import org.jackhuang.hmcl.util.readFullyAsByteArray
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.io.File
|
||||
|
||||
private fun lessThan32(b: ByteArray, startIndex: Int): Int {
|
||||
for (i in startIndex until b.size)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@ package org.jackhuang.hmcl.game
|
||||
* @see Version.resolve
|
||||
*/
|
||||
interface VersionProvider {
|
||||
|
||||
/**
|
||||
* Does the version of id exist?
|
||||
* @param id the id of version
|
||||
@@ -36,4 +37,5 @@ interface VersionProvider {
|
||||
* @return the version you want
|
||||
*/
|
||||
fun getVersion(id: String): Version
|
||||
|
||||
}
|
||||
@@ -18,14 +18,16 @@
|
||||
package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.game.DownloadType
|
||||
import org.jackhuang.hmcl.game.GameException
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.game.LaunchOptions
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.coroutines.experimental.EmptyCoroutineContext.plus
|
||||
|
||||
/**
|
||||
* @param versionId The version to be launched.
|
||||
@@ -220,7 +222,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
}
|
||||
}
|
||||
|
||||
override fun launch(): JavaProcess {
|
||||
override fun launch(): ManagedProcess {
|
||||
|
||||
// To guarantee that when failed to generate code, we will not call precalled command
|
||||
val builder = ProcessBuilder(rawCommandLine)
|
||||
@@ -237,7 +239,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
builder.directory(repository.getRunDirectory(version.id))
|
||||
.environment().put("APPDATA", options.gameDir.absoluteFile.parent)
|
||||
val p = JavaProcess(builder.start(), rawCommandLine)
|
||||
val p = ManagedProcess(builder.start(), rawCommandLine)
|
||||
if (listener == null)
|
||||
startMonitors(p)
|
||||
else
|
||||
@@ -245,8 +247,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
return p
|
||||
}
|
||||
|
||||
fun launchAsync(): TaskResult<JavaProcess> {
|
||||
return object : TaskResult<JavaProcess>() {
|
||||
fun launchAsync(): TaskResult<ManagedProcess> {
|
||||
return object : TaskResult<ManagedProcess>() {
|
||||
override val id = LAUNCH_ASYNC_ID
|
||||
override fun execute() {
|
||||
result = launch()
|
||||
@@ -279,20 +281,20 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
return scriptFile
|
||||
}
|
||||
|
||||
private fun startMonitors(javaProcess: JavaProcess) {
|
||||
javaProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(javaProcess.process.inputStream)::run)
|
||||
javaProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(javaProcess.process.errorStream)::run)
|
||||
private fun startMonitors(managedProcess: ManagedProcess) {
|
||||
managedProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(managedProcess.process.inputStream)::run)
|
||||
managedProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(managedProcess.process.errorStream)::run)
|
||||
}
|
||||
|
||||
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
||||
processListener.setProcess(javaProcess)
|
||||
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); javaProcess.lines += line }.apply { start() }
|
||||
javaProcess.relatedThreads += logHandler
|
||||
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, { logHandler.newLine(it) } )::run)
|
||||
javaProcess.relatedThreads += stdout
|
||||
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); javaProcess.lines += it })::run)
|
||||
javaProcess.relatedThreads += stderr
|
||||
javaProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
|
||||
private fun startMonitors(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
||||
processListener.setProcess(managedProcess)
|
||||
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() }
|
||||
managedProcess.relatedThreads += logHandler
|
||||
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { logHandler.newLine(it) } )::run)
|
||||
managedProcess.relatedThreads += stdout
|
||||
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run)
|
||||
managedProcess.relatedThreads += stderr
|
||||
managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -21,16 +21,15 @@ 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.util.JavaProcess
|
||||
import org.jackhuang.hmcl.util.ManagedProcess
|
||||
import org.jackhuang.hmcl.util.containsOne
|
||||
import org.jackhuang.hmcl.util.guessLogLineError
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @param process the process to wait for
|
||||
* @param watcher the callback that will be called after process stops.
|
||||
*/
|
||||
internal class ExitWaiter(val process: JavaProcess, val joins: Collection<Thread>, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable {
|
||||
internal class ExitWaiter(val process: ManagedProcess, val joins: Collection<Thread>, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable {
|
||||
override fun run() {
|
||||
try {
|
||||
process.process.waitFor()
|
||||
|
||||
@@ -21,7 +21,7 @@ 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 org.jackhuang.hmcl.util.ManagedProcess
|
||||
import java.io.File
|
||||
|
||||
abstract class Launcher(
|
||||
@@ -34,7 +34,7 @@ abstract class Launcher(
|
||||
|
||||
val version: Version = repository.getVersion(versionId).resolve(repository)
|
||||
abstract val rawCommandLine: List<String>
|
||||
abstract fun launch(): JavaProcess
|
||||
abstract fun launch(): ManagedProcess
|
||||
|
||||
/**
|
||||
* @param file The file path without extension
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout.
|
||||
* Also supports plain logs.
|
||||
*/
|
||||
internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread() {
|
||||
val reader = XMLReaderFactory.createXMLReader().apply {
|
||||
@@ -53,6 +54,9 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called to stop [Log4jHandler] manually.
|
||||
*/
|
||||
fun onStopped() {
|
||||
if (interrupted.get())
|
||||
return
|
||||
@@ -66,7 +70,7 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called in [ProcessListener.onErrorLog] and [ProcessListener.onLog] manually.
|
||||
* New XML line.
|
||||
*/
|
||||
fun newLine(content: String) =
|
||||
Scheduler.COMPUTATION.schedule {
|
||||
@@ -102,6 +106,7 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
|
||||
l = Log4jLevel.ERROR
|
||||
}
|
||||
"log4j_Message" -> readingMessage = true
|
||||
"log4j_Throwable" -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.util.JavaProcess
|
||||
import org.jackhuang.hmcl.util.Log4jLevel
|
||||
import org.jackhuang.hmcl.util.ManagedProcess
|
||||
|
||||
interface ProcessListener {
|
||||
/**
|
||||
* When a game launched, this method will be called to get the new process.
|
||||
* You should not override this method when your ProcessListener is shared with all processes.
|
||||
*/
|
||||
fun setProcess(process: JavaProcess) {}
|
||||
fun setProcess(process: ManagedProcess) {}
|
||||
|
||||
/**
|
||||
* Called when receiving a log from stdout/stderr.
|
||||
|
||||
@@ -23,6 +23,12 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.logging.Level
|
||||
|
||||
/**
|
||||
* Pump the given input stream.
|
||||
* @param inputStream the input stream to pump
|
||||
* @param callback receives each line
|
||||
*
|
||||
*/
|
||||
internal class StreamPump @JvmOverloads constructor(
|
||||
private val inputStream: InputStream,
|
||||
private val callback: (String) -> Unit = {}
|
||||
|
||||
@@ -99,14 +99,27 @@ class CurseForgeModpackManifestFile (
|
||||
val url: URL get() = "https://minecraft.curseforge.com/projects/$projectID/files/$fileID/download".toURL()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param f the CurseForge modpack file.
|
||||
* @return the manifest.
|
||||
*/
|
||||
fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest {
|
||||
ZipFile(f).use { zipFile ->
|
||||
val entry = zipFile.getEntry("manifest.json") ?: throw IOException("Manifest.json not found. Not a valid CurseForge modpack.")
|
||||
val entry = zipFile.getEntry("manifest.json") ?: throw IOException("`manifest.json` not found. Not a valid CurseForge modpack.")
|
||||
val json = zipFile.getInputStream(entry).readFullyAsString()
|
||||
return GSON.fromJson<CurseForgeModpackManifest>(json) ?: throw JsonParseException("Manifest.json not found. Not a valid CurseForge modpack.")
|
||||
return GSON.fromJson<CurseForgeModpackManifest>(json) ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a downloaded CurseForge modpack.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param zipFile the CurseForge modpack file.
|
||||
* @param manifest The manifest content of given CurseForge modpack.
|
||||
* @param name the new version name
|
||||
* @see readCurseForgeModpackManifest
|
||||
*/
|
||||
class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() {
|
||||
val repository = dependencyManager.repository
|
||||
init {
|
||||
@@ -136,7 +149,8 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
|
||||
f.fileName = f.url.detectFileName(dependencyManager.proxy)
|
||||
dependencies += FileDownloadTask(f.url, run.resolve("mods").resolve(f.fileName), proxy = dependencyManager.proxy)
|
||||
} catch (e: IOException) {
|
||||
// ignore it and retry next time.
|
||||
// Because in China, the CurseForge is too difficult to visit.
|
||||
// So if failed, ignore it and retry next time.
|
||||
}
|
||||
++finished
|
||||
updateProgress(1.0 * finished / manifest.files.size)
|
||||
@@ -146,6 +160,12 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the CurseForge version.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param version the existent and physical version.
|
||||
*/
|
||||
class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, version: String): Task() {
|
||||
val repository = dependencyManager.repository
|
||||
val run = repository.getRunDirectory(version)
|
||||
@@ -161,6 +181,8 @@ class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, vers
|
||||
else {
|
||||
manifest = GSON.fromJson<CurseForgeModpackManifest>(repository.getVersionRoot(version).resolve("manifest.json").readText())!!
|
||||
|
||||
// Because in China, the CurseForge is too difficult to visit.
|
||||
// So caching the file name is necessary.
|
||||
for (f in manifest!!.files) {
|
||||
if (f.fileName.isBlank())
|
||||
dependents += task { f.fileName = f.url.detectFileName(proxy) }
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.util.typeOf
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class ForgeModMetadata(
|
||||
class ForgeModMetadata @JvmOverloads internal constructor(
|
||||
@SerializedName("modid")
|
||||
val modId: String = "",
|
||||
val name: String = "",
|
||||
@@ -42,6 +42,9 @@ class ForgeModMetadata(
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Read Forge mod ModInfo.
|
||||
*/
|
||||
fun fromFile(modFile: File): ModInfo {
|
||||
ZipFile(modFile).use {
|
||||
val entry = it.getEntry("mcmod.info") ?: throw JsonParseException("File $modFile is not a Forge mod.")
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
package org.jackhuang.hmcl.mod
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.GSON
|
||||
import org.jackhuang.hmcl.util.fromJson
|
||||
import org.jackhuang.hmcl.util.readFullyAsString
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class LiteModMetadata (
|
||||
class LiteModMetadata @JvmOverloads internal constructor(
|
||||
val name: String = "",
|
||||
val version: String = "",
|
||||
val mcversion: String = "",
|
||||
@@ -37,6 +39,9 @@ class LiteModMetadata (
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Read LiteLoader mod ModInfo.
|
||||
*/
|
||||
fun fromFile(modFile: File): ModInfo {
|
||||
ZipFile(modFile).use {
|
||||
val entry = it.getEntry("litemod.json")
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import org.jackhuang.hmcl.util.property.ImmediateBooleanProperty
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.ImmediateBooleanProperty
|
||||
import org.jackhuang.hmcl.util.getValue
|
||||
import org.jackhuang.hmcl.util.setValue
|
||||
import java.io.File
|
||||
|
||||
class ModInfo (
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
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) {
|
||||
|
||||
@@ -19,6 +19,13 @@ package org.jackhuang.hmcl.task
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
|
||||
/**
|
||||
* A task that combines two tasks and make sure [pred] runs before [succ].
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() {
|
||||
override val hidden: Boolean = true
|
||||
|
||||
|
||||
@@ -19,28 +19,39 @@ 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 org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.math.BigInteger
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.math.BigInteger
|
||||
import java.util.logging.Level
|
||||
|
||||
/**
|
||||
* A task that can download a file online.
|
||||
*
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param proxy the proxy.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class FileDownloadTask @JvmOverloads constructor(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
|
||||
|
||||
/**
|
||||
* Once downloading fails, this event will be fired to gain the substitute URL.
|
||||
*/
|
||||
var onFailed = EventManager<FailedEvent<URL>>()
|
||||
|
||||
private var rFile: RandomAccessFile? = null
|
||||
private var stream: InputStream? = null
|
||||
|
||||
fun closeFiles() {
|
||||
private fun closeFiles() {
|
||||
rFile?.closeQuietly()
|
||||
rFile = null
|
||||
stream?.closeQuietly()
|
||||
|
||||
@@ -25,6 +25,17 @@ import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* A task that can read the content of a remote text file.
|
||||
*
|
||||
* @param url the URL of remote text file.
|
||||
* @param encoding the encoding/charset of the remote text file.
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param proxy the proxy.
|
||||
* @param id the result variable id, see [Task.variables]
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY, override val id: String = ID): TaskResult<String>() {
|
||||
override val scheduler: Scheduler = Scheduler.IO
|
||||
|
||||
@@ -65,6 +76,9 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The default task result ID.
|
||||
*/
|
||||
const val ID = "http_get"
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
/**
|
||||
* The tasks that provides a way to execute tasks parallelly.
|
||||
*
|
||||
* @param tasks the tasks that can be executed parallelly.
|
||||
*/
|
||||
class ParallelTask(vararg tasks: Task): Task() {
|
||||
override val hidden: Boolean = true
|
||||
override val dependents: Collection<Task> = listOf(*tasks)
|
||||
|
||||
@@ -17,13 +17,25 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
import javafx.application.Platform
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
* Determines how a task is executed.
|
||||
*
|
||||
* @see [Task.scheduler]
|
||||
*/
|
||||
interface Scheduler {
|
||||
/**
|
||||
* Schedules the given task.
|
||||
* @return the future, null if future is not supported.
|
||||
*/
|
||||
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
|
||||
|
||||
/**
|
||||
* Schedules the given task.
|
||||
* @return the future, null if future is not supported.
|
||||
*/
|
||||
fun schedule(block: Callable<Unit>): Future<*>?
|
||||
|
||||
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
|
||||
@@ -58,7 +70,7 @@ interface Scheduler {
|
||||
}
|
||||
|
||||
private class SchedulerExecutorService(val executorService: ExecutorService) : Scheduler {
|
||||
override fun schedule(block: Callable<Unit>) = executorService.submit(block)
|
||||
override fun schedule(block: Callable<Unit>): Future<*> = executorService.submit(block)
|
||||
}
|
||||
|
||||
companion object Schedulers {
|
||||
@@ -90,13 +102,44 @@ interface Scheduler {
|
||||
return null
|
||||
}
|
||||
}
|
||||
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
||||
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
||||
|
||||
/**
|
||||
* A scheduler for JavaFX UI operations.
|
||||
*/
|
||||
val JAVAFX: Scheduler = SchedulerImpl(javafx.application.Platform::runLater)
|
||||
|
||||
/**
|
||||
* A scheduler for Swing UI operations.
|
||||
*/
|
||||
val SWING: Scheduler = SchedulerImpl(javax.swing.SwingUtilities::invokeLater)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* The default scheduler for tasks to be executed.
|
||||
* @see [Task.scheduler]
|
||||
*/
|
||||
val DEFAULT = NEW_THREAD
|
||||
|
||||
/**
|
||||
* Shut down all executor services to guarantee that the application can stop implicitly.
|
||||
*/
|
||||
fun shutdown() {
|
||||
CACHED_EXECUTOR.shutdown()
|
||||
IO_EXECUTOR.shutdown()
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
class SilentException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
}
|
||||
/**
|
||||
* If a task throws [SilentException], the task will be marked as failure but do not log the stacktrace.
|
||||
*/
|
||||
class SilentException : Exception()
|
||||
@@ -22,12 +22,15 @@ import javafx.beans.property.ReadOnlyDoubleWrapper
|
||||
import javafx.beans.property.ReadOnlyStringProperty
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import org.jackhuang.hmcl.event.EventManager
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
import org.jackhuang.hmcl.util.updateAsync
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
* Disposable task.
|
||||
*
|
||||
* @see [TaskExecutor]
|
||||
*/
|
||||
abstract class Task {
|
||||
/**
|
||||
|
||||
@@ -19,11 +19,13 @@ package org.jackhuang.hmcl.task
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Future
|
||||
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
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
/**
|
||||
* A task that has a result.
|
||||
*/
|
||||
abstract class TaskResult<V> : Task() {
|
||||
open var result: V? = null
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
/**
|
||||
* Mark if the model is immutable.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Immutable
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
/**
|
||||
* A map that support auto casting.
|
||||
*/
|
||||
class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) {
|
||||
|
||||
fun clear() = impl.clear()
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
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()
|
||||
|
||||
|
||||
@@ -20,15 +20,12 @@ 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 com.google.gson.stream.JsonWriter
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Type
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@@ -64,6 +64,10 @@ fun InputStream.copyToAndClose(dest: OutputStream) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cmd the command line
|
||||
* @return the final command line
|
||||
*/
|
||||
fun makeCommand(cmd: List<String>): String {
|
||||
val cmdbuf = StringBuilder(120)
|
||||
for (i in cmd.indices) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.property
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import javafx.beans.property.*
|
||||
import javafx.beans.value.ChangeListener
|
||||
@@ -24,12 +24,26 @@ import java.io.Serializable
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Represents a Java installation.
|
||||
*/
|
||||
data class JavaVersion internal constructor(
|
||||
@SerializedName("location")
|
||||
val binary: File,
|
||||
val longVersion: String,
|
||||
val platform: Platform) : Serializable
|
||||
{
|
||||
/**
|
||||
* The major version of Java installation.
|
||||
*
|
||||
* @see JAVA_X
|
||||
* @see JAVA_9
|
||||
* @see JAVA_8
|
||||
* @see JAVA_7
|
||||
* @see JAVA_6
|
||||
* @see JAVA_5
|
||||
* @see UNKNOWN
|
||||
*/
|
||||
val version = parseVersion(longVersion)
|
||||
|
||||
companion object {
|
||||
@@ -110,10 +124,12 @@ data class JavaVersion internal constructor(
|
||||
return path.resolve("java")
|
||||
}
|
||||
|
||||
private val currentJava: JavaVersion = JavaVersion(
|
||||
binary = getJavaFile(File(System.getProperty("java.home"))),
|
||||
longVersion = System.getProperty("java.version"),
|
||||
platform = Platform.PLATFORM)
|
||||
private val currentJava: JavaVersion by lazy {
|
||||
JavaVersion(
|
||||
binary = getJavaFile(File(System.getProperty("java.home"))),
|
||||
longVersion = System.getProperty("java.version"),
|
||||
platform = Platform.PLATFORM)
|
||||
}
|
||||
fun fromCurrentEnvironment() = currentJava
|
||||
|
||||
init {
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
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 {
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
@file:JvmName("HMCLog")
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import javafx.scene.paint.Color
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.logging.*
|
||||
import java.util.logging.Formatter
|
||||
import javafx.scene.paint.Color
|
||||
import java.util.regex.Pattern
|
||||
|
||||
val LOG = Logger.getLogger("HMCL").apply {
|
||||
|
||||
@@ -20,25 +20,58 @@ package org.jackhuang.hmcl.util
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
class JavaProcess(
|
||||
/**
|
||||
* The managed process.
|
||||
*
|
||||
* @param process the raw system process that this instance manages.
|
||||
* @param commands the command line of [process].
|
||||
* @see [org.jackhuang.hmcl.launch.ExitWaiter]
|
||||
* @see [org.jackhuang.hmcl.launch.StreamPump]
|
||||
*/
|
||||
class ManagedProcess(
|
||||
val process: Process,
|
||||
val commands: List<String>
|
||||
) {
|
||||
/**
|
||||
* To save some information you need.
|
||||
*/
|
||||
val properties = mutableMapOf<String, Any>()
|
||||
|
||||
/**
|
||||
* The standard output/error lines.
|
||||
*/
|
||||
val lines: Queue<String> = ConcurrentLinkedQueue<String>()
|
||||
|
||||
/**
|
||||
* The related threads.
|
||||
*
|
||||
* If a thread is monitoring this raw process,
|
||||
* you are required to add the instance to [relatedThreads].
|
||||
*/
|
||||
val relatedThreads = mutableListOf<Thread>()
|
||||
|
||||
/**
|
||||
* True if the managed process is running.
|
||||
*/
|
||||
val isRunning: Boolean = try {
|
||||
process.exitValue()
|
||||
true
|
||||
} catch (ex: IllegalThreadStateException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* The exit code of raw process.
|
||||
*/
|
||||
val exitCode: Int get() = process.exitValue()
|
||||
|
||||
override fun toString() = "JavaProcess[commands=$commands, isRunning=$isRunning]"
|
||||
|
||||
/**
|
||||
* Destroys the raw process and other related threads that are monitoring this raw process.
|
||||
*/
|
||||
fun stop() {
|
||||
process.destroy()
|
||||
relatedThreads.forEach(Thread::interrupt)
|
||||
}
|
||||
|
||||
override fun toString() = "ManagedProcess[commands=$commands, isRunning=$isRunning]"
|
||||
}
|
||||
@@ -19,20 +19,19 @@ package org.jackhuang.hmcl.util
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import kotlin.text.Charsets
|
||||
|
||||
private val XTM = object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
|
||||
|
||||
@@ -25,17 +25,31 @@ import java.lang.management.ManagementFactory
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Represents the operating system.
|
||||
*/
|
||||
enum class OS {
|
||||
@SerializedName("windows")
|
||||
/**
|
||||
* Microsoft Windows.
|
||||
*/
|
||||
WINDOWS,
|
||||
@SerializedName("linux")
|
||||
/**
|
||||
* Linux and Unix like OS, including Solaris.
|
||||
*/
|
||||
LINUX,
|
||||
@SerializedName("osx")
|
||||
/**
|
||||
* Mac OS X.
|
||||
*/
|
||||
OSX,
|
||||
@SerializedName("unknown")
|
||||
/**
|
||||
* Unknown operating system.
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The current operating system.
|
||||
*/
|
||||
val CURRENT_OS: OS by lazy {
|
||||
System.getProperty("os.name").toLowerCase(Locale.US).run {
|
||||
when {
|
||||
@@ -47,12 +61,18 @@ enum class OS {
|
||||
}
|
||||
}
|
||||
|
||||
val TOTAL_MEMORY: Long by lazy {
|
||||
/**
|
||||
* The total memory/MB this computer have.
|
||||
*/
|
||||
val TOTAL_MEMORY: Int by lazy {
|
||||
val bytes = ReflectionHelper.invoke<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize")
|
||||
if (bytes == null) 1024
|
||||
else bytes / 1024 / 1024
|
||||
else (bytes / 1024 / 1024).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* The suggested memory size/MB for Minecraft to allocate.
|
||||
*/
|
||||
val SUGGESTED_MEMORY: Int by lazy {
|
||||
val memory = TOTAL_MEMORY / 4
|
||||
(Math.round(1.0 * memory / 128.0) * 128).toInt()
|
||||
@@ -61,13 +81,27 @@ enum class OS {
|
||||
val PATH_SEPARATOR: String = File.pathSeparator
|
||||
val FILE_SEPARATOR: String = File.separator
|
||||
val LINE_SEPARATOR: String by lazy(System::lineSeparator)
|
||||
|
||||
/**
|
||||
* The system default encoding.
|
||||
*/
|
||||
val ENCODING: String by lazy {
|
||||
System.getProperty("sun.jnu.encoding", Charset.defaultCharset().name())
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of current operating system.
|
||||
*/
|
||||
val SYSTEM_VERSION: String by lazy { System.getProperty("os.version") }
|
||||
|
||||
/**
|
||||
* The arch of current operating system.
|
||||
*/
|
||||
val SYSTEM_ARCH: String by lazy { System.getProperty("os.arch") }
|
||||
|
||||
/**
|
||||
* Set the content of clipboard.
|
||||
*/
|
||||
fun setClipboard(string: String) {
|
||||
val clipboard = Clipboard.getSystemClipboard()
|
||||
clipboard.setContent(ClipboardContent().apply {
|
||||
|
||||
@@ -20,11 +20,18 @@ package org.jackhuang.hmcl.util
|
||||
import com.google.gson.*
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* The platform that indicates which the platform of operating system is, 64-bit or 32-bit.
|
||||
* Of course, 128-bit and 16-bit is not supported.
|
||||
*/
|
||||
enum class Platform(val bit: String) {
|
||||
BIT_32("32"),
|
||||
BIT_64("64"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
/**
|
||||
* The json serializer to [Platform]
|
||||
*/
|
||||
companion object Serializer: JsonSerializer<Platform>, JsonDeserializer<Platform> {
|
||||
|
||||
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Platform? {
|
||||
@@ -45,10 +52,16 @@ enum class Platform(val bit: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The platform of current Java Environment.
|
||||
*/
|
||||
val PLATFORM: Platform by lazy {
|
||||
if (IS_64_BIT) BIT_64 else BIT_32
|
||||
}
|
||||
|
||||
/**
|
||||
* True if current Java Environment is 64-bit.
|
||||
*/
|
||||
val IS_64_BIT: Boolean by lazy {
|
||||
val arch = System.getProperty("sun.arch.data.model") ?: System.getProperty("os.arch")
|
||||
arch.contains("64")
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import java.util.HashMap
|
||||
import java.util.*
|
||||
|
||||
object RandomUserAgent {
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import java.lang.reflect.AccessibleObject
|
||||
import sun.misc.Unsafe
|
||||
import java.lang.reflect.AccessibleObject
|
||||
import java.lang.reflect.Method
|
||||
import java.security.PrivilegedExceptionAction
|
||||
import java.security.AccessController
|
||||
import java.security.PrivilegedExceptionAction
|
||||
|
||||
object ReflectionHelper {
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.jackhuang.hmcl.util
|
||||
|
||||
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>>()
|
||||
private val valuesImpl: MutableCollection<V> = valuer()
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The formatted version number represents a version string.
|
||||
*/
|
||||
abstract class VersionNumber: Comparable<VersionNumber> {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -47,6 +50,9 @@ abstract class VersionNumber: Comparable<VersionNumber> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a version string contains alphabets, a [StringVersionNumber] will be constructed.
|
||||
*/
|
||||
class StringVersionNumber internal constructor(val version: String): VersionNumber() {
|
||||
override fun compareTo(other: VersionNumber): Int {
|
||||
if (other !is StringVersionNumber) return 0
|
||||
@@ -64,6 +70,9 @@ class StringVersionNumber internal constructor(val version: String): VersionNumb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a version string formats x.x.x.x, a [IntVersionNumber] will be generated.
|
||||
*/
|
||||
class IntVersionNumber internal constructor(val version: List<Int>): VersionNumber() {
|
||||
|
||||
override fun compareTo(other: VersionNumber): Int {
|
||||
|
||||
@@ -17,24 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* 功能:把 src 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件
|
||||
|
||||
* @param src 源文件夹
|
||||
* *
|
||||
* @param destZip 压缩生成的zip文件路径。
|
||||
* *
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your
|
||||
* * modified pathName
|
||||
* *
|
||||
* *
|
||||
* @throws java.io.IOException 压缩失败或无法读取
|
||||
* Compress the given directory to a zip file.
|
||||
*
|
||||
* @param src the source directory or a file.
|
||||
* @param destZip the location of dest zip file.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName
|
||||
* @throws IOException
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
@@ -53,14 +48,11 @@ fun zip(src: File, destZip: File, pathNameCallback: ((String, Boolean) -> String
|
||||
|
||||
/**
|
||||
* Zip file.
|
||||
|
||||
* @param src 源文件夹
|
||||
* @param basePath file directory that will be compressed
|
||||
* *
|
||||
* @param zos zip文件的os
|
||||
* *
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your
|
||||
* * modified pathName, null if you dont want this file zipped
|
||||
* @param src source directory to be compressed.
|
||||
* @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src]
|
||||
* @param zos the [ZipOutputStream] of dest zip file.
|
||||
* @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName, null if you dont want this file zipped
|
||||
* @throws IOException
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun zipFile(src: File,
|
||||
@@ -98,17 +90,11 @@ private fun zipFile(src: File,
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件压缩成zip文件
|
||||
|
||||
* @param zip zip文件路径
|
||||
* *
|
||||
* @param dest 待压缩文件根目录
|
||||
* *
|
||||
* @param callback will be called for every entry in the zip file,
|
||||
* * returns false if you dont want this file unzipped.
|
||||
* *
|
||||
* *
|
||||
* @throws java.io.IOException 解压失败或无法写入
|
||||
* Decompress the given zip file to a directory.
|
||||
* @param zip the source zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param callback will be called for every entry in the zip file, returns false if you dont want this file unzipped.
|
||||
* @throws IOException
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
@@ -158,21 +144,16 @@ fun unzip(zip: File, dest: File, callback: ((String) -> Boolean)? = null, ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件压缩成zip文件
|
||||
|
||||
* @param zip zip文件路径
|
||||
* *
|
||||
* @param dest 待压缩文件根目录
|
||||
* *
|
||||
* @param callback will be called for every entry in the zip file,
|
||||
* * returns false if you dont want this file unzipped.
|
||||
* *
|
||||
* *
|
||||
* @throws java.io.IOException 解压失败或无法写入
|
||||
* Decompress the subdirectory of given zip file.
|
||||
* @param zip the source zip file.
|
||||
* @param dest the dest directory.
|
||||
* @param subDirectory the subdirectory of the zip file to be decompressed.
|
||||
* @param ignoreExistentFile true if skip all existent files.
|
||||
* @throws IOException
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExistsFile: Boolean = true) {
|
||||
fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExistentFile: Boolean = true) {
|
||||
val buf = ByteArray(1024)
|
||||
dest.mkdirs()
|
||||
ZipInputStream(zip.inputStream()).use { zipFile ->
|
||||
@@ -204,7 +185,7 @@ fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExistsF
|
||||
if (!subdir.exists())
|
||||
subdir.mkdir()
|
||||
}
|
||||
if (ignoreExistsFile && File(strtemp).exists())
|
||||
if (ignoreExistentFile && File(strtemp).exists())
|
||||
continue
|
||||
File(strtemp).outputStream().use({ fos ->
|
||||
zipFile.copyTo(fos, buf)
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl
|
||||
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||
@@ -30,9 +30,6 @@ import org.jackhuang.hmcl.launch.ProcessListener
|
||||
import org.jackhuang.hmcl.util.makeCommand
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskListener
|
||||
import org.jackhuang.hmcl.util.detectFileName
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
|
||||
Reference in New Issue
Block a user