Turn to java

This commit is contained in:
huangyuhui
2018-01-01 23:12:59 +08:00
parent d8be9abcc5
commit a40c5fdd40
307 changed files with 17342 additions and 10414 deletions

View File

@@ -22,11 +22,13 @@ import javafx.application.Platform
import javafx.stage.Stage
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.ui.Controllers
import org.jackhuang.hmcl.ui.runOnUiThread
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.util.OS
import org.jackhuang.hmcl.util.Constants
import org.jackhuang.hmcl.util.Logging.LOG
import org.jackhuang.hmcl.util.NetworkUtils
import org.jackhuang.hmcl.util.OperatingSystem
import java.io.File
import java.util.logging.Level
@@ -34,7 +36,7 @@ fun i18n(key: String): String {
try {
return Main.RESOURCE_BUNDLE.getString(key)
} catch (e: Exception) {
LOG.log(Level.WARNING, "Cannot find key $key in resource bundle", e)
LOG.log(Level.SEVERE, "Cannot find key $key in resource bundle", e)
return key
}
}
@@ -62,20 +64,21 @@ class Main : Application() {
@JvmStatic
fun main(args: Array<String>) {
DEFAULT_USER_AGENT = { "Hello Minecraft! Launcher" }
NetworkUtils.setUserAgentSupplier { "Hello Minecraft! Launcher" }
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
launch(Main::class.java, *args)
}
fun getWorkingDirectory(folder: String): File {
val userhome = System.getProperty("user.home", ".")
return when (OS.CURRENT_OS) {
OS.LINUX -> File(userhome, ".$folder/")
OS.WINDOWS -> {
return when (OperatingSystem.CURRENT_OS) {
OperatingSystem.LINUX -> File(userhome, ".$folder/")
OperatingSystem.WINDOWS -> {
val appdata: String? = System.getenv("APPDATA")
File(appdata ?: userhome, ".$folder/")
}
OS.OSX -> File(userhome, "Library/Application Support/" + folder)
OperatingSystem.OSX -> File(userhome, "Library/Application Support/" + folder)
else -> File(userhome, "$folder/")
}
}
@@ -89,7 +92,7 @@ class Main : Application() {
fun stopWithoutJavaFXPlatform() = runOnUiThread {
Controllers.stage.close()
Scheduler.shutdown()
Schedulers.shutdown()
}
val RESOURCE_BUNDLE = Settings.locale.resourceBundle

View File

@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.FileDownloadTask
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.ui.DEFAULT_ICON
import org.jackhuang.hmcl.ui.DialogController
@@ -48,11 +49,13 @@ object AccountHelper {
SkinLoadTask(account, proxy, true)
private class SkinLoadTask(val account: YggdrasilAccount, val proxy: Proxy, val refresh: Boolean = false): Task() {
override val scheduler = Scheduler.IO
override val dependencies = mutableListOf<Task>()
override fun getScheduler() = Schedulers.io()
private val dependencies = mutableListOf<Task>()
override fun getDependencies() = dependencies
override fun execute() {
if (account.canLogIn && (account.selectedProfile == null || refresh))
if (account.canLogIn() && (account.selectedProfile == null || refresh))
DialogController.logIn(account)
val profile = account.selectedProfile ?: return
val name = profile.name ?: return
@@ -60,7 +63,7 @@ object AccountHelper {
val file = getSkinFile(name)
if (!refresh && file.exists())
return
dependencies += FileDownloadTask(url.toURL(), file, proxy = proxy)
dependencies += FileDownloadTask(url.toURL(), file, proxy)
}
}

View File

@@ -23,8 +23,8 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.util.LOG
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.util.Logging.LOG
import org.jackhuang.hmcl.util.fromJson
import java.io.File
import java.io.IOException
@@ -72,7 +72,7 @@ class HMCLGameRepository(val profile: Profile, baseDirectory: File)
@Synchronized
override fun refreshVersionsImpl() {
Scheduler.NEW_THREAD.schedule {
Schedulers.newThread().schedule {
versionSettings.clear()
super.refreshVersionsImpl()

View File

@@ -18,7 +18,8 @@
package org.jackhuang.hmcl.game
import com.google.gson.JsonParseException
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.task.Task
@@ -26,6 +27,8 @@ import java.io.File
import java.io.IOException
import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Constants.GSON
import org.jackhuang.hmcl.util.Logging.LOG
import java.util.ArrayList
/**
@@ -38,39 +41,42 @@ import java.util.ArrayList
*/
@Throws(IOException::class, JsonParseException::class)
fun readHMCLModpackManifest(f: File): Modpack {
val manifestJson = f.readTextZipEntry("modpack.json")
val manifestJson = CompressingUtils.readTextZipEntry(f, "modpack.json")
val manifest = GSON.fromJson<Modpack>(manifestJson) ?: throw JsonParseException("`modpack.json` not found. $f is not a valid HMCL modpack.")
val gameJson = f.readTextZipEntry("minecraft/pack.json")
val gameJson = CompressingUtils.readTextZipEntry(f, "minecraft/pack.json")
val game = GSON.fromJson<Version>(gameJson) ?: throw JsonParseException("`minecraft/pack.json` not found. $f iot a valid HMCL modpack.")
return if (game.jar == null)
if (manifest.gameVersion.isNullOrBlank()) throw JsonParseException("Cannot recognize the game version of modpack $f.")
else manifest.copy(manifest = HMCLModpackManifest)
else manifest.copy(manifest = HMCLModpackManifest, gameVersion = game.jar!!)
else manifest.setManifest(HMCLModpackManifest)
else manifest.setManifest(HMCLModpackManifest).setGameVersion(game.jar)
}
object HMCLModpackManifest
class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val name: String): Task() {
class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val version: String): Task() {
private val dependency = profile.dependency
private val repository = profile.repository
override val dependencies = mutableListOf<Task>()
override val dependents = mutableListOf<Task>()
private val dependencies = mutableListOf<Task>()
private val dependents = mutableListOf<Task>()
override fun getDependencies() = dependencies
override fun getDependents() = dependents
init {
check(!repository.hasVersion(name), { "Version $name already exists." })
val json = zipFile.readTextZipEntry("minecraft/pack.json")
check(!repository.hasVersion(version), { "Version $version already exists." })
val json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json")
var version = GSON.fromJson<Version>(json)!!
version = version.copy(jar = null)
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync()
dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync()
version = version.setJar(null)
dependents += dependency.gameBuilder().name(this.version).gameVersion(modpack.gameVersion!!).buildAsync()
dependencies += VersionJsonSaveTask(repository, version) // override the json created by buildAsync()
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) }
onDone() += { event -> if (event.isFailed) repository.removeVersionFromDisk(this.version) }
}
private val run = repository.getRunDirectory(name)
private val run = repository.getRunDirectory(version)
override fun execute() {
zipFile.uncompressTo(run, "minecraft/", callback = { it != "minecraft/pack.json" }, ignoreExistentFile = false)
CompressingUtils.unzip(zipFile, run, "minecraft/", { it != "minecraft/pack.json" }, false)
}
}
@@ -120,10 +126,12 @@ class HMCLModpackExportTask @JvmOverloads constructor(
private val whitelist: List<String>,
private val modpack: Modpack,
private val output: File,
override val id: String = ID): TaskResult<ZipEngine>() {
private val id: String = ID): TaskResult<ZipEngine>() {
override fun getId() = id
init {
onDone += { event -> if (event.failed) output.delete() }
onDone() += { event -> if (event.isFailed) output.delete() }
}
override fun execute() {
@@ -148,8 +156,8 @@ class HMCLModpackExportTask @JvmOverloads constructor(
val mv = repository.getVersion(version).resolve(repository)
val gameVersion = minecraftVersion(repository.getVersionJar(version)) ?: throw IllegalStateException("Cannot parse the version of $version")
zip.putTextFile(GSON.toJson(mv.copy(jar = gameVersion)), "minecraft/pack.json") // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(GSON.toJson(modpack.copy(gameVersion = gameVersion)), "modpack.json") // Newer HMCL only reads 'gameVersion' field.
zip.putTextFile(GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json") // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json") // Newer HMCL only reads 'gameVersion' field.
}
}

View File

@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.auth.AuthInfo
import org.jackhuang.hmcl.auth.AuthenticationException
import org.jackhuang.hmcl.launch.DefaultLauncher
import org.jackhuang.hmcl.launch.ProcessListener
import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask
import org.jackhuang.hmcl.mod.CurseCompletionTask
import org.jackhuang.hmcl.setting.LauncherVisibility
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.setting.VersionSetting
@@ -31,10 +31,10 @@ import org.jackhuang.hmcl.task.*
import org.jackhuang.hmcl.ui.*
import org.jackhuang.hmcl.util.Log4jLevel
import org.jackhuang.hmcl.util.ManagedProcess
import org.jackhuang.hmcl.util.task
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
object LauncherHelper {
private val launchingStepsPane = LaunchingStepsPane()
val PROCESS = ConcurrentLinkedQueue<ManagedProcess>()
@@ -49,13 +49,13 @@ object LauncherHelper {
val setting = profile.getVersionSetting(selectedVersion)
Controllers.dialog(launchingStepsPane)
task(Scheduler.JAVAFX) { emitStatus(LoadingState.DEPENDENCIES) }
task(Schedulers.javafx()) { emitStatus(LoadingState.DEPENDENCIES) }
.then(dependency.checkGameCompletionAsync(version))
.then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.MODS) })
.then(CurseForgeModpackCompletionTask(dependency, selectedVersion))
.then(task(Schedulers.javafx()) { emitStatus(LoadingState.MODS) })
.then(CurseCompletionTask(dependency, selectedVersion))
.then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LOGIN) })
.then(task(Schedulers.javafx()) { emitStatus(LoadingState.LOGIN) })
.then(task {
try {
it["account"] = account.logIn(Settings.proxy)
@@ -65,7 +65,7 @@ object LauncherHelper {
}
})
.then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LAUNCHING) })
.then(task(Schedulers.javafx()) { emitStatus(LoadingState.LAUNCHING) })
.then(task {
it["launcher"] = HMCLGameLauncher(
repository = repository,
@@ -84,12 +84,12 @@ object LauncherHelper {
.executor()
.apply {
taskListener = object : TaskListener {
taskListener = object : TaskListener() {
var finished = 0
override fun onFinished(task: Task) {
++finished
runOnUiThread { launchingStepsPane.pgsTasks.progress = 1.0 * finished / totTask.get() }
runOnUiThread { launchingStepsPane.pgsTasks.progress = 1.0 * finished / runningTasks }
}
override fun onTerminate() {
@@ -179,9 +179,14 @@ object LauncherHelper {
if (exitType != ProcessListener.ExitType.NORMAL && logWindow == null){
runOnUiThread {
LogWindow().apply {
for ((line, level) in logs)
logLine(line, level)
show()
Schedulers.newThread().schedule {
waitForShown()
runOnUiThread {
for ((line, level) in logs)
logLine(line, level)
}
}
}
}
}

View File

@@ -17,21 +17,18 @@
*/
package org.jackhuang.hmcl.game
import org.jackhuang.hmcl.mod.InstanceConfiguration
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest
import org.jackhuang.hmcl.mod.readMMCModpackManifest
import org.jackhuang.hmcl.mod.*
import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.toStringOrEmpty
import java.io.File
fun readModpackManifest(f: File): Modpack {
try {
return readCurseForgeModpackManifest(f)
return CurseManifest.readCurseForgeModpackManifest(f);
} catch (e: Exception) {
// ignore it, not a valid CurseForge modpack.
}
@@ -44,7 +41,7 @@ fun readModpackManifest(f: File): Modpack {
}
try {
val manifest = readMMCModpackManifest(f)
val manifest = MultiMCInstanceConfiguration.readMultiMCModpackManifest(f)
return manifest
} catch (e: Exception) {
// ignore it, not a valid MMC modpack.
@@ -53,36 +50,36 @@ fun readModpackManifest(f: File): Modpack {
throw IllegalArgumentException("Modpack file $f is not supported.")
}
fun InstanceConfiguration.toVersionSetting(vs: VersionSetting) {
fun MultiMCInstanceConfiguration.toVersionSetting(vs: VersionSetting) {
vs.usesGlobal = false
vs.gameDirType = EnumGameDirectory.VERSION_FOLDER
if (overrideJavaLocation) {
if (isOverrideJavaLocation) {
vs.javaDir = javaPath.toStringOrEmpty()
}
if (overrideMemory) {
if (isOverrideMemory) {
vs.permSize = permGen.toStringOrEmpty()
if (maxMemory != null)
vs.maxMemory = maxMemory!!
vs.minMemory = minMemory
}
if (overrideCommands) {
if (isOverrideCommands) {
vs.wrapper = wrapperCommand.orEmpty()
vs.precalledCommand = preLaunchCommand.orEmpty()
}
if (overrideJavaArgs) {
if (isOverrideJavaArgs) {
vs.javaArgs = jvmArgs.orEmpty()
}
if (overrideConsole) {
vs.showLogs = showConsole
if (isOverrideConsole) {
vs.showLogs = isShowConsole
}
if (overrideWindow) {
vs.fullscreen = fullscreen
if (isOverrideWindow) {
vs.fullscreen = isFullscreen
if (width != null)
vs.width = width!!
if (height != null)
@@ -90,10 +87,10 @@ fun InstanceConfiguration.toVersionSetting(vs: VersionSetting) {
}
}
class MMCInstallVersionSettingTask(private val profile: Profile, val manifest: InstanceConfiguration, val name: String): Task() {
override val scheduler = Scheduler.JAVAFX
class MMCInstallVersionSettingTask(private val profile: Profile, val manifest: MultiMCInstanceConfiguration, private val version: String): Task() {
override fun getScheduler() = Schedulers.javafx()
override fun execute() {
val vs = profile.specializeVersionSetting(name)!!
val vs = profile.specializeVersionSetting(version)!!
manifest.toVersionSetting(vs)
}
}

View File

@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.setting
import com.google.gson.*
import javafx.beans.InvalidationListener
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.HMCLGameRepository
import org.jackhuang.hmcl.mod.ModManager
@@ -49,7 +49,7 @@ class Profile(name: String = "Default", initialGameDir: File = File(".minecraft"
init {
gameDirProperty.onChange { repository.changeDirectory(it!!) }
selectedVersionProperty.onInvalidated(this::verifySelectedVersion)
EVENT_BUS.channel<RefreshedVersionsEvent>() += { event -> if (event.source == repository) verifySelectedVersion() }
EventBus.EVENT_BUS.channel<RefreshedVersionsEvent>().register { event -> if (event.source == repository) verifySelectedVersion() }
}
private fun verifySelectedVersion() = runOnUiThread {

View File

@@ -27,9 +27,10 @@ import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Logging.LOG
import java.io.File
import java.io.IOException
import java.net.Authenticator
@@ -43,7 +44,7 @@ object Settings {
val GSON = GsonBuilder()
.registerTypeAdapter(VersionSetting::class.java, VersionSetting)
.registerTypeAdapter(Profile::class.java, Profile)
.registerTypeAdapter(File::class.java, FileTypeAdapter)
.registerTypeAdapter(File::class.java, FileTypeAdapter.INSTANCE)
.setPrettyPrinting().create()
const val DEFAULT_PROFILE = "Default"
@@ -209,14 +210,14 @@ object Settings {
var downloadProvider: DownloadProvider
get() = when (SETTINGS.downloadtype) {
0 -> MojangDownloadProvider
1 -> BMCLAPIDownloadProvider
else -> MojangDownloadProvider
0 -> MojangDownloadProvider.INSTANCE
1 -> BMCLAPIDownloadProvider.INSTANCE
else -> MojangDownloadProvider.INSTANCE
}
set(value) {
SETTINGS.downloadtype = when (value) {
MojangDownloadProvider -> 0
BMCLAPIDownloadProvider -> 1
MojangDownloadProvider.INSTANCE -> 0
BMCLAPIDownloadProvider.INSTANCE -> 1
else -> 0
}
}
@@ -275,14 +276,14 @@ object Settings {
get() {
if (!hasProfile(SETTINGS.selectedProfile)) {
SETTINGS.selectedProfile = DEFAULT_PROFILE
Scheduler.COMPUTATION.schedule { onProfileChanged() }
Schedulers.computation().schedule { onProfileChanged() }
}
return getProfile(SETTINGS.selectedProfile)
}
set(value) {
if (hasProfile(value.name) && value.name != SETTINGS.selectedProfile) {
SETTINGS.selectedProfile = value.name
Scheduler.COMPUTATION.schedule { onProfileChanged() }
Schedulers.computation().schedule { onProfileChanged() }
}
}
@@ -327,14 +328,14 @@ object Settings {
}
val flag = getProfileMap().remove(ver) != null
if (flag)
Scheduler.COMPUTATION.schedule { onProfileLoading() }
Schedulers.computation().schedule { onProfileLoading() }
return flag
}
internal fun onProfileChanged() {
selectedProfile.repository.refreshVersions()
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
EventBus.EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
}
/**
@@ -342,7 +343,7 @@ object Settings {
* Invoked by loading GUI phase.
*/
fun onProfileLoading() {
EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS))
EventBus.EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS))
onProfileChanged()
}
}

View File

@@ -21,7 +21,9 @@ import javafx.scene.text.Font
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.AccountFactory
import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.OfflineAccountFactory
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.ui.construct.UTF8Control
@@ -35,19 +37,18 @@ object Proxies {
}
object DownloadProviders {
val DOWNLOAD_PROVIDERS = listOf(MojangDownloadProvider, BMCLAPIDownloadProvider)
val DOWNLOAD_PROVIDERS = listOf(MojangDownloadProvider.INSTANCE, BMCLAPIDownloadProvider.INSTANCE)
fun getDownloadProvider(index: Int) = DOWNLOAD_PROVIDERS.getOrElse(index, { MojangDownloadProvider })
fun getDownloadProvider(index: Int) = DOWNLOAD_PROVIDERS.getOrElse(index, { MojangDownloadProvider.INSTANCE })
}
object Accounts {
val OFFLINE_ACCOUNT_KEY = "offline"
val YGGDRASIL_ACCOUNT_KEY = "yggdrasil"
val ACCOUNTS = listOf(OfflineAccount, YggdrasilAccount)
val ACCOUNT_FACTORY = mapOf<String, AccountFactory<*>>(
OFFLINE_ACCOUNT_KEY to OfflineAccount,
YGGDRASIL_ACCOUNT_KEY to YggdrasilAccount
OFFLINE_ACCOUNT_KEY to OfflineAccountFactory.INSTANCE,
YGGDRASIL_ACCOUNT_KEY to YggdrasilAccountFactory.INSTANCE
)
fun getAccountType(account: Account): String {

View File

@@ -21,6 +21,10 @@ import com.google.gson.*
import javafx.beans.InvalidationListener
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.setting.Settings.proxyHost
import org.jackhuang.hmcl.setting.Settings.proxyPass
import org.jackhuang.hmcl.setting.Settings.proxyPort
import org.jackhuang.hmcl.setting.Settings.proxyUser
import org.jackhuang.hmcl.util.*
import java.io.File
import java.io.IOException
@@ -70,7 +74,7 @@ class VersionSetting() {
/**
* The maximum memory that JVM can allocate for heap.
*/
val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY)
val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY.toInt())
var maxMemory: Int by maxMemoryProperty
/**
@@ -232,28 +236,28 @@ class VersionSetting() {
@Throws(IOException::class)
fun toLaunchOptions(gameDir: File): LaunchOptions {
return LaunchOptions(
gameDir = gameDir,
java = javaVersion ?: JavaVersion.fromCurrentEnvironment(),
versionName = Main.TITLE,
profileName = Main.TITLE,
minecraftArgs = minecraftArgs,
javaArgs = javaArgs,
maxMemory = maxMemory,
minMemory = minMemory,
metaspace = permSize.toIntOrNull(),
width = width,
height = height,
fullscreen = fullscreen,
serverIp = serverIp,
wrapper = wrapper,
proxyHost = Settings.proxyHost,
proxyPort = Settings.proxyPort,
proxyUser = Settings.proxyUser,
proxyPass = Settings.proxyPass,
precalledCommand = precalledCommand,
noGeneratedJVMArgs = noJVMArgs
)
return LaunchOptions.Builder()
.setGameDir(gameDir)
.setJava(javaVersion ?: JavaVersion.fromCurrentEnvironment())
.setVersionName(Main.TITLE)
.setProfileName(Main.TITLE)
.setMinecraftArgs(minecraftArgs)
.setJavaArgs(javaArgs)
.setMaxMemory(maxMemory)
.setMinMemory(minMemory)
.setMetaspace(permSize.toIntOrNull())
.setWidth(width)
.setHeight(height)
.setFullscreen(fullscreen)
.setServerIp(serverIp)
.setWrapper(wrapper)
.setProxyHost(Settings.proxyHost)
.setProxyPort(Settings.proxyPort)
.setProxyUser(Settings.proxyUser)
.setProxyPass(Settings.proxyPass)
.setPrecalledCommand(precalledCommand)
.setNoGeneratedJVMArgs(noJVMArgs)
.create()
}
companion object Serializer: JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
@@ -264,7 +268,7 @@ class VersionSetting() {
addProperty("usesGlobal", src.usesGlobal)
addProperty("javaArgs", src.javaArgs)
addProperty("minecraftArgs", src.minecraftArgs)
addProperty("maxMemory", if (src.maxMemory <= 0) OS.SUGGESTED_MEMORY else src.maxMemory)
addProperty("maxMemory", if (src.maxMemory <= 0) OperatingSystem.SUGGESTED_MEMORY.toInt() else src.maxMemory)
addProperty("minMemory", src.minMemory)
addProperty("permSize", src.permSize)
addProperty("width", src.width)
@@ -290,8 +294,8 @@ class VersionSetting() {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): VersionSetting? {
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
var maxMemoryN = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive, OS.SUGGESTED_MEMORY)
if (maxMemoryN <= 0) maxMemoryN = OS.SUGGESTED_MEMORY
var maxMemoryN = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive, OperatingSystem.SUGGESTED_MEMORY.toInt())
if (maxMemoryN <= 0) maxMemoryN = OperatingSystem.SUGGESTED_MEMORY.toInt()
return VersionSetting().apply {
usesGlobal = json["usesGlobal"]?.asBoolean ?: false

View File

@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.game.AccountHelper
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import java.util.concurrent.Callable
class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane() {
@@ -83,10 +84,10 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane(
btnRefresh.setOnMouseClicked {
pgsSkin.isVisible = true
AccountHelper.refreshSkinAsync(account)
.subscribe(Scheduler.JAVAFX) { loadSkin() }
.subscribe(Schedulers.javafx()) { loadSkin() }
}
AccountHelper.loadSkinAsync(account)
.subscribe(Scheduler.JAVAFX) { loadSkin() }
.subscribe(Schedulers.javafx()) { loadSkin() }
}
if (account is OfflineAccount) { // Offline Account cannot be refreshed,

View File

@@ -29,15 +29,17 @@ import javafx.scene.control.ToggleGroup
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.OfflineAccountFactory
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.taskResult
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.onChangeAndOperate
import org.jackhuang.hmcl.util.taskResult
class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@@ -126,8 +128,8 @@ class AccountsPage() : StackPane(), DecoratorPage {
taskResult("create_account") {
try {
val account = when (type) {
0 -> OfflineAccount.fromUsername(username)
1 -> YggdrasilAccount.fromUsername(username, password)
0 -> OfflineAccountFactory.INSTANCE.fromUsername(username)
1 -> YggdrasilAccountFactory.INSTANCE.fromUsername(username, password)
else -> throw UnsupportedOperationException()
}
@@ -136,7 +138,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
} catch (e: Exception) {
e
}
}.subscribe(Scheduler.JAVAFX) {
}.subscribe(Schedulers.javafx()) {
val account: Any = it["create_account"]
if (account is Account) {
Settings.addAccount(account)

View File

@@ -25,8 +25,8 @@ import javafx.scene.layout.Region
import javafx.stage.Stage
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.util.JavaVersion
import org.jackhuang.hmcl.util.task
object Controllers {
lateinit var scene: Scene private set

View File

@@ -43,6 +43,7 @@ import javafx.scene.shape.Rectangle
import javafx.util.Duration
import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Logging.LOG
import java.io.File
import java.io.IOException
import java.util.logging.Level
@@ -196,8 +197,8 @@ fun JFXMasonryPane.resetChildren(children: List<Node>) {
fun openFolder(f: File) {
f.mkdirs()
val path = f.absolutePath
when (OS.CURRENT_OS) {
OS.OSX ->
when (OperatingSystem.CURRENT_OS) {
OperatingSystem.OSX ->
try {
Runtime.getRuntime().exec(arrayOf("/usr/bin/open", path));
} catch (ex: IOException) {

View File

@@ -20,13 +20,13 @@ package org.jackhuang.hmcl.ui
import javafx.fxml.FXML
import javafx.scene.control.ScrollPane
import javafx.scene.layout.VBox
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.game.minecraftVersion
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.ui.download.InstallWizardProvider
import org.jackhuang.hmcl.util.task
import java.util.*
class InstallerController {
@@ -59,9 +59,9 @@ class InstallerController {
val removeAction = { _: InstallerItem ->
val newList = LinkedList(version.libraries)
newList.remove(library)
VersionJSONSaveTask(profile.repository, version.copy(libraries = newList))
VersionJsonSaveTask(profile.repository, version.setLibraries(newList))
.with(task { profile.repository.refreshVersions() })
.with(task(Scheduler.JAVAFX) { loadVersion(this.profile, this.versionId) })
.with(task(Schedulers.javafx()) { loadVersion(this.profile, this.versionId) })
.start()
}
if (library.groupId.equals("net.minecraftforge", ignoreCase = true) && library.artifactId.equals("forge", ignoreCase = true)) {

View File

@@ -22,13 +22,15 @@ import javafx.scene.paint.Paint
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.game.AccountHelper
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.IconedItem
import org.jackhuang.hmcl.ui.construct.RipplerContainer
import org.jackhuang.hmcl.util.channel
import org.jackhuang.hmcl.util.onChangeAndOperate
import org.jackhuang.hmcl.util.plusAssign
import java.util.*
class LeftPaneController(private val leftPane: AdvancedListBox) {
@@ -56,8 +58,8 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
.startCategory(i18n("ui.label.profile"))
.add(profilePane)
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
EventBus.EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EventBus.EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
Controllers.decorator.addMenuButton.setOnMouseClicked {
Controllers.decorator.showPage(ProfilePage(null))

View File

@@ -38,6 +38,7 @@ import org.jackhuang.hmcl.util.*
import org.w3c.dom.Document
import org.w3c.dom.Node
import java.util.concurrent.Callable
import java.util.concurrent.CountDownLatch
class LogWindow : Stage() {
val fatalProperty = SimpleIntegerProperty(0)
@@ -47,6 +48,7 @@ class LogWindow : Stage() {
val debugProperty = SimpleIntegerProperty(0)
val impl = LogWindowImpl()
val latch = CountDownLatch(1)
init {
scene = Scene(impl, 800.0, 480.0)
@@ -74,6 +76,8 @@ class LogWindow : Stage() {
}
}
fun waitForShown() = latch.await()
inner class LogWindowImpl: StackPane() {
@FXML lateinit var webView: WebView
@FXML lateinit var btnFatals: ToggleButton
@@ -97,6 +101,7 @@ class LogWindow : Stage() {
document = engine.document
body = document.getElementsByTagName("body").item(0)
engine.executeScript("limitedLogs=${Settings.logLines};")
latch.countDown()
}
}

View File

@@ -29,17 +29,19 @@ import javafx.scene.image.Image
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.GameVersion.minecraftVersion
import org.jackhuang.hmcl.game.LauncherHelper
import org.jackhuang.hmcl.game.minecraftVersion
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.RipplerContainer
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.channel
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.plusAssign
/**
* @see /assets/fxml/main.fxml
@@ -59,9 +61,9 @@ class MainPage : StackPane(), DecoratorPage {
btnLaunch.limitWidth(40.0)
btnLaunch.limitHeight(40.0)
EVENT_BUS.channel<RefreshedVersionsEvent>() += { -> loadVersions() }
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
EventBus.EVENT_BUS.channel<RefreshedVersionsEvent>() += { -> loadVersions() }
EventBus.EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EventBus.EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") }
btnRefresh.setOnMouseClicked { Settings.selectedProfile.repository.refreshVersions() }

View File

@@ -27,9 +27,10 @@ import javafx.stage.FileChooser
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.util.onChange
import org.jackhuang.hmcl.util.onChangeAndOperateWeakly
import org.jackhuang.hmcl.util.task
import java.util.*
class ModController {
@@ -77,7 +78,7 @@ class ModController {
modManager.removeMods(versionId, modInfo)
loadMods(modManager, versionId)
}.apply {
modInfo.activeProperty.onChange {
modInfo.activeProperty().onChange {
if (it)
styleClass -= "disabled"
else
@@ -91,7 +92,7 @@ class ModController {
runOnUiThread { rootPane.children += contentPane }
it["list"] = list
}
}.subscribe(Scheduler.JAVAFX) { variables ->
}.subscribe(Schedulers.javafx()) { variables ->
parentTab.selectionModel.selectedItemProperty().onChangeAndOperateWeakly {
if (it?.userData == this) {
modPane.children.setAll(variables.get<List<ModItem>>("list"))
@@ -106,6 +107,6 @@ class ModController {
chooser.extensionFilters.setAll(FileChooser.ExtensionFilter("Mod", "*.jar", "*.zip", "*.litemod"))
val res = chooser.showOpenDialog(Controllers.stage) ?: return
task { modManager.addMod(versionId, res) }
.subscribe(task(Scheduler.JAVAFX) { loadMods(modManager, versionId) })
.subscribe(task(Schedulers.javafx()) { loadMods(modManager, versionId) })
}
}

View File

@@ -53,10 +53,10 @@ class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : Bo
style = "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"
JFXDepthManager.setDepth(this, 1)
lblModFileName.text = info.fileName
lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.mcversion}, Authors: ${info.authors}"
lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.gameVersion}, Authors: ${info.authors}"
chkEnabled.isSelected = info.isActive
chkEnabled.selectedProperty().onChange {
info.activeProperty.set(it)
info.activeProperty().set(it)
}
}

View File

@@ -24,25 +24,21 @@ import javafx.scene.Node
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.control.Toggle
import javafx.scene.control.ToggleGroup
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.VBox
import javafx.stage.DirectoryChooser
import javafx.stage.FileChooser
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.ui.construct.ComponentList
import org.jackhuang.hmcl.ui.construct.MultiFileItem
import org.jackhuang.hmcl.ui.construct.NumberValidator
import org.jackhuang.hmcl.util.JavaVersion
import org.jackhuang.hmcl.util.OS
import org.jackhuang.hmcl.util.OperatingSystem
import org.jackhuang.hmcl.util.task
class VersionSettingsController {
var lastVersionSetting: VersionSetting? = null
@@ -74,7 +70,7 @@ class VersionSettingsController {
lateinit var versionId: String
fun initialize() {
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB"
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OperatingSystem.TOTAL_MEMORY}MB"
scroll.smoothScrolling()
@@ -102,9 +98,9 @@ class VersionSettingsController {
task {
it["list"] = JavaVersion.getJREs().values.map { javaVersion ->
javaItem.createChildren(javaVersion.longVersion, javaVersion.binary.absolutePath, javaVersion)
javaItem.createChildren(javaVersion.version, javaVersion.binary.absolutePath, javaVersion)
}
}.subscribe(Scheduler.JAVAFX) {
}.subscribe(Schedulers.javafx()) {
javaItem.loadChildren(it.get<Collection<Node>>("list"))
}
@@ -175,7 +171,7 @@ class VersionSettingsController {
if (newValue == javaItem.radioCustom) { // Custom
version.java = "Custom"
} else {
version.java = ((newValue as JFXRadioButton).userData as JavaVersion).longVersion
version.java = ((newValue as JFXRadioButton).userData as JavaVersion).version
}
}
javaItem.group.properties[javaGroupKey] = listener
@@ -220,7 +216,7 @@ class VersionSettingsController {
private fun initJavaSubtitle(version: VersionSetting) {
task { it["java"] = version.javaVersion }
.subscribe(task(Scheduler.JAVAFX) { javaItem.subtitle = it.get<JavaVersion?>("java")?.binary?.absolutePath ?: "Invalid Java Directory" })
.subscribe(task(Schedulers.javafx()) { javaItem.subtitle = it.get<JavaVersion?>("java")?.binary?.absolutePath ?: "Invalid Java Directory" })
}
private fun initGameDirSubtitle(version: VersionSetting) {

View File

@@ -26,10 +26,11 @@ import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.auth.AuthInfo
import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.taskResult
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.util.taskResult
class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, private val success: (AuthInfo) -> Unit, private val failed: () -> Unit) : StackPane() {
@FXML lateinit var lblUsername: Label
@@ -54,12 +55,12 @@ class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, privat
lblCreationWarning.text = ""
taskResult("login") {
try {
val account = YggdrasilAccount.fromUsername(username, password)
val account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password)
account.logIn(Settings.proxy)
} catch (e: Exception) {
e
}
}.subscribe(Scheduler.JAVAFX) {
}.subscribe(Schedulers.javafx()) {
val account: Any = it["login"]
if (account is AuthInfo) {
success(account)

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.download
import javafx.scene.Node
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.game.HMCLModpackInstallTask
import org.jackhuang.hmcl.game.HMCLModpackManifest
import org.jackhuang.hmcl.game.MMCInstallVersionSettingTask
@@ -27,9 +26,9 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardProvider
import org.jackhuang.hmcl.util.task
import java.io.File
class DownloadWizardProvider(): WizardProvider() {
@@ -79,11 +78,11 @@ class DownloadWizardProvider(): WizardProvider() {
}
return when (modpack.manifest) {
is CurseForgeModpackManifest -> CurseForgeModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseForgeModpackManifest, name)
is CurseManifest -> CurseInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseManifest, name)
is HMCLModpackManifest -> HMCLModpackInstallTask(profile, selectedFile, modpack, name)
is InstanceConfiguration -> MMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as InstanceConfiguration, name) with MMCInstallVersionSettingTask(profile, modpack.manifest as InstanceConfiguration, name)
is MultiMCInstanceConfiguration -> MultiMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as MultiMCInstanceConfiguration, name).with(MMCInstallVersionSettingTask(profile, modpack.manifest as MultiMCInstanceConfiguration, name))
else -> throw Error()
} with finalizeTask
}.with(finalizeTask)
}
override fun finish(settings: MutableMap<String, Any>): Any? {

View File

@@ -2,20 +2,12 @@ package org.jackhuang.hmcl.ui.download
import javafx.scene.Node
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.game.HMCLModpackInstallTask
import org.jackhuang.hmcl.game.HMCLModpackManifest
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask
import org.jackhuang.hmcl.mod.CurseForgeModpackManifest
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.ui.wizard.WizardController
import org.jackhuang.hmcl.ui.wizard.WizardProvider
import org.jackhuang.hmcl.util.task
import java.io.File
class InstallWizardProvider(val profile: Profile, val gameVersion: String, val version: Version, val forge: String? = null, val liteloader: String? = null, val optifine: String? = null): WizardProvider() {
@@ -27,20 +19,20 @@ class InstallWizardProvider(val profile: Profile, val gameVersion: String, val v
var ret = task {}
if (settings.containsKey("forge"))
ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "forge", settings["forge"] as String)
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "forge", settings["forge"] as String))
if (settings.containsKey("liteloader"))
ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "liteloader", settings["liteloader"] as String)
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "liteloader", settings["liteloader"] as String))
if (settings.containsKey("optifine"))
ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "optifine", settings["optifine"] as String)
ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "optifine", settings["optifine"] as String))
return ret with task { profile.repository.refreshVersions() }
return ret.with(task { profile.repository.refreshVersions() })
}
override fun createPage(controller: WizardController, step: Int, settings: MutableMap<String, Any>): Node {
return when (step) {
0 -> AdditionalInstallersPage(this, controller, profile.repository, BMCLAPIDownloadProvider)
0 -> AdditionalInstallersPage(this, controller, profile.repository, BMCLAPIDownloadProvider.INSTANCE)
else -> throw IllegalStateException()
}
}

View File

@@ -24,6 +24,7 @@ import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.RemoteVersion
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.task.TaskExecutor
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
import org.jackhuang.hmcl.ui.animation.TransitionHandler
@@ -53,9 +54,9 @@ class VersionsPage(private val controller: WizardController, private val gameVer
}
override fun refresh() {
executor = versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) {
executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx()) {
val versions = ArrayList(versionList.getVersions(gameVersion))
versions.sortWith(RemoteVersion)
versions.sortWith(RemoteVersion.RemoteVersionComparator.INSTANCE)
for (version in versions) {
list.items.add(VersionsPageItem(version))
}

View File

@@ -34,10 +34,12 @@ class ExportWizardProvider(private val profile: Profile, private val version: St
@Suppress("UNCHECKED_CAST")
return HMCLModpackExportTask(profile.repository, version, settings[ModpackFileSelectionPage.MODPACK_FILE_SELECTION] as List<String>,
Modpack(
name = settings[ModpackInfoPage.MODPACK_NAME] as String,
author = settings[ModpackInfoPage.MODPACK_AUTHOR] as String,
version = settings[ModpackInfoPage.MODPACK_VERSION] as String,
description = settings[ModpackInfoPage.MODPACK_DESCRIPTION] as String
settings[ModpackInfoPage.MODPACK_NAME] as String,
settings[ModpackInfoPage.MODPACK_AUTHOR] as String,
settings[ModpackInfoPage.MODPACK_VERSION] as String,
null,
settings[ModpackInfoPage.MODPACK_DESCRIPTION] as String,
null
), settings[ModpackInfoPage.MODPACK_FILE] as File)
}

View File

@@ -23,10 +23,7 @@ import javafx.application.Platform
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.TaskExecutor
import org.jackhuang.hmcl.task.TaskListener
import org.jackhuang.hmcl.task.*
import java.util.*
import kotlin.concurrent.thread
@@ -94,28 +91,28 @@ interface AbstractWizardDisplayer : WizardDisplayer {
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
task.with(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
task.with(org.jackhuang.hmcl.util.task(Schedulers.javafx()) {
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
}).executor().apply {
@Suppress("NAME_SHADOWING")
taskListener = object : TaskListener {
taskListener = object : TaskListener() {
override fun onReady(task: Task) {
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) }
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks) }
}
override fun onFinished(task: Task) {
Platform.runLater {
label.text = task.title
label.text = task.name
++finishedTasks
tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks)
}
}
override fun onFailed(task: Task, throwable: Throwable) {
Platform.runLater {
label.text = task.title
label.text = task.name
++finishedTasks
tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks)
}
}

View File

@@ -37,10 +37,12 @@ import java.util.jar.Pack200
import java.util.jar.JarOutputStream
import org.jackhuang.hmcl.util.*
import java.net.URISyntaxException
import org.jackhuang.hmcl.util.OS
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.ui.alert
import org.jackhuang.hmcl.util.Constants.GSON
import org.jackhuang.hmcl.util.Logging.LOG
import org.jackhuang.hmcl.util.VersionNumber
import java.net.Proxy
import java.net.URI
class AppDataUpgrader : IUpgrader {
@@ -123,18 +125,18 @@ class AppDataUpgrader : IUpgrader {
else {
var url = URL_PUBLISH
if (map != null)
if (map.containsKey(OS.CURRENT_OS.checkedName))
url = map.get(OS.CURRENT_OS.checkedName)!!
else if (map.containsKey(OS.UNKNOWN.checkedName))
url = map.get(OS.UNKNOWN.checkedName)!!
if (map.containsKey(OperatingSystem.CURRENT_OS.checkedName))
url = map.get(OperatingSystem.CURRENT_OS.checkedName)!!
else if (map.containsKey(OperatingSystem.UNKNOWN.checkedName))
url = map.get(OperatingSystem.UNKNOWN.checkedName)!!
try {
java.awt.Desktop.getDesktop().browse(URI(url))
} catch (e: URISyntaxException) {
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
OS.setClipboard(url)
OperatingSystem.setClipboard(url)
} catch (e: IOException) {
LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e)
OS.setClipboard(url)
OperatingSystem.setClipboard(url)
}
}
@@ -144,16 +146,17 @@ class AppDataUpgrader : IUpgrader {
class AppDataUpgraderPackGzTask(downloadLink: String, private val newestVersion: String, private val expectedHash: String) : Task() {
private val tempFile: File = File.createTempFile("hmcl", ".pack.gz")
override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash))
private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash))
override fun getDependents() = dependents
init {
onDone += { event -> if (event.failed) tempFile.delete() }
onDone() += { event -> if (event.isFailed) tempFile.delete() }
}
override fun execute() {
val json = HashMap<String, String>()
var f = getSelf(newestVersion)
if (!f.parentFile.makeDirectory())
if (!FileUtils.makeDirectory(f.parentFile))
throw IOException("Failed to make directories: " + f.parent)
var i = 0
@@ -187,15 +190,15 @@ class AppDataUpgrader : IUpgrader {
}
class AppDataUpgraderJarTask(downloadLink: String, private val newestVersion: String, expectedHash: String) : Task() {
override var title = "Upgrade"
set(value) {}
private val tempFile = File.createTempFile("hmcl", ".jar")
init {
onDone += { event -> if (event.failed) tempFile.delete() }
name = "Upgrade"
onDone() += { event -> if (event.isFailed) tempFile.delete() }
}
override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash))
private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash))
override fun getDependents() = dependents
override fun execute() {
val json = HashMap<String, String>()

View File

@@ -21,6 +21,8 @@ import java.io.IOException
import com.google.gson.JsonSyntaxException
import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.Constants.GSON
import org.jackhuang.hmcl.util.Logging.LOG
import java.util.logging.Level
@@ -58,10 +60,10 @@ class UpdateChecker(var base: VersionNumber, var type: String) {
*/
fun process(showMessage: Boolean): TaskResult<VersionNumber> {
return object : TaskResult<VersionNumber>() {
override val id = "update_checker.process"
override fun getId() = "update_checker.process"
override fun execute() {
if (newVersion == null) {
versionString = ("http://huangyuhui.duapp.com/info.php?type=$type").toURL().doGet()
versionString = NetworkUtils.doGet("http://huangyuhui.duapp.com/info.php?type=$type".toURL())
newVersion = VersionNumber.asVersion(versionString!!)
}
@@ -83,12 +85,12 @@ class UpdateChecker(var base: VersionNumber, var type: String) {
@Synchronized
fun requestDownloadLink(): TaskResult<Map<String, String>> {
return object : TaskResult<Map<String, String>>() {
override val id = "update_checker.request_download_link"
override fun getId() = "update_checker.request_download_link"
override fun execute() {
@Suppress("UNCHECKED_CAST")
if (download_link == null)
try {
download_link = GSON.fromJson(("http://huangyuhui.duapp.com/update_link.php?type=$type").toURL().doGet(), Map::class.java) as Map<String, String>
download_link = GSON.fromJson(NetworkUtils.doGet("http://huangyuhui.duapp.com/update_link.php?type=$type".toURL()), Map::class.java) as Map<String, String>
} catch (e: JsonSyntaxException) {
LOG.log(Level.WARNING, "Failed to get update link.", e)
} catch (e: IOException) {

View File

@@ -17,10 +17,25 @@
*/
package org.jackhuang.hmcl.util
import com.google.gson.Gson
import com.google.gson.JsonParseException
import com.google.gson.reflect.TypeToken
import javafx.beans.property.Property
import javafx.event.Event.fireEvent
import org.jackhuang.hmcl.event.EventBus
import org.jackhuang.hmcl.event.EventManager
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Schedulers
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.Constants.UI_THREAD_SCHEDULER
import java.io.InputStream
import java.lang.reflect.Type
import java.net.URL
import java.rmi.activation.Activatable.unregister
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.HashMap
inline fun ignoreException(func: () -> Unit) {
try {
@@ -34,28 +49,6 @@ inline fun ignoreThrowable(func: () -> Unit) {
} catch (ignore: Throwable) {}
}
fun <K, V> unmodifiableMap(map: Map<K, V>?): Map<K, V>? =
if (map == null) null
else Collections.unmodifiableMap(map)
fun <K, V> copyMap(map: Map<K, V>?): MutableMap<K, V>? =
if (map == null) null
else HashMap(map)
fun <T> unmodifiableList(list: List<T>?): List<T>? =
if (list == null) null
else Collections.unmodifiableList(list)
fun <T> copyList(list: List<T>?): MutableList<T>? =
if (list == null) null
else LinkedList(list)
fun <T> merge(vararg c: Collection<T>?): List<T> = LinkedList<T>().apply {
for (a in c)
if (a != null)
addAll(a)
}
fun isBlank(str: String?) = str?.isBlank() ?: true
fun isNotBlank(str: String?) = !isBlank(str)
@@ -84,42 +77,7 @@ fun String.asVersion(): String? {
fun Any?.toStringOrEmpty() = this?.toString().orEmpty()
fun parseParams(addBefore: String, objects: Collection<*>, addAfter: String): String {
return parseParams(addBefore, objects.toTypedArray(), addAfter)
}
fun parseParams(addBefore: String, objects: Array<*>, addAfter: String): String {
return parseParams({ addBefore }, objects, { addAfter })
}
fun parseParams(beforeFunc: (Any?) -> String, params: Array<*>?, afterFunc: (Any?) -> String): String {
if (params == null)
return ""
val sb = StringBuilder()
for (i in params.indices) {
val param = params[i]
val addBefore = beforeFunc(param)
val addAfter = afterFunc(param)
if (i > 0)
sb.append(addAfter).append(addBefore)
if (param == null)
sb.append("null")
else if (param.javaClass.isArray) {
sb.append("[")
if (param is Array<*>) {
sb.append(parseParams(beforeFunc, param, afterFunc))
} else
for (j in 0..java.lang.reflect.Array.getLength(param) - 1) {
if (j > 0)
sb.append(addAfter)
sb.append(addBefore).append(java.lang.reflect.Array.get(param, j))
}
sb.append("]")
} else
sb.append(addBefore).append(params[i])
}
return sb.toString()
}
fun String.toURL() = URL(this)
fun Collection<String>.containsOne(vararg matcher: String): Boolean {
for (a in this)
@@ -131,9 +89,34 @@ fun Collection<String>.containsOne(vararg matcher: String): Boolean {
fun <T> Property<in T>.updateAsync(newValue: T, update: AtomicReference<T>) {
if (update.getAndSet(newValue) == null) {
UI_THREAD_SCHEDULER {
UI_THREAD_SCHEDULER.accept(Runnable {
val current = update.getAndSet(null)
this.value = current
}
})
}
}
}
inline fun <reified T> typeOf(): Type = object : TypeToken<T>() {}.type
inline fun <reified T> Gson.fromJson(json: String): T? = fromJson<T>(json, T::class.java)
inline fun <reified T> Gson.fromJsonQuietly(json: String): T? {
try {
return fromJson<T>(json)
} catch (json: JsonParseException) {
return null
}
}
fun task(scheduler: Scheduler = Schedulers.defaultScheduler(), closure: (AutoTypingMap<String>) -> Unit): Task = Task.of(closure, scheduler)
fun <V> taskResult(id: String, callable: Callable<V>): TaskResult<V> = Task.ofResult(id, callable)
fun <V> taskResult(id: String, callable: (AutoTypingMap<String>) -> V): TaskResult<V> = Task.ofResult(id, callable)
fun InputStream.readFullyAsString() = IOUtils.readFullyAsString(this)
inline fun <reified T : EventObject> EventBus.channel() = channel(T::class.java)
operator fun <T : EventObject> EventManager<T>.plusAssign(func: (T) -> Unit) = register(func)
operator fun <T : EventObject> EventManager<T>.plusAssign(func: () -> Unit) = register(func)
operator fun <T : EventObject> EventManager<T>.minusAssign(func: (T) -> Unit) = unregister(func)
operator fun <T : EventObject> EventManager<T>.minusAssign(func: () -> Unit) = unregister(func)
operator fun <T : EventObject> EventManager<T>.invoke(event: T) = fireEvent(event)

View File

@@ -0,0 +1,40 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import java.net.Proxy;
import java.util.Map;
/**
*
* @author huangyuhui
*/
public abstract class Account {
public abstract String getUsername();
public AuthInfo logIn() throws AuthenticationException {
return logIn(Proxy.NO_PROXY);
}
public abstract AuthInfo logIn(Proxy proxy) throws AuthenticationException;
public abstract void logOut();
public abstract Map<Object, Object> toStorage();
}

View File

@@ -15,17 +15,21 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.task
package org.jackhuang.hmcl.auth;
import java.util.Map;
/**
* The tasks that provides a way to execute tasks parallelly.
* Fails when some of [tasks] failed.
*
* @param tasks the tasks that can be executed parallelly.
* @author huangyuhui
*/
class ParallelTask(vararg tasks: Task): Task() {
override val hidden: Boolean = true
override val dependents: Collection<Task> = listOf(*tasks)
public abstract class AccountFactory<T extends Account> {
override fun execute() {}
}
public final T fromUsername(String username) {
return fromUsername(username, "");
}
public abstract T fromUsername(String username, String password);
public abstract T fromStorage(Map<Object, Object> storage);
}

View File

@@ -0,0 +1,96 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
/**
*
* @author huangyuhui
*/
public final class AuthInfo {
private final String username;
private final String userId;
private final String authToken;
private final UserType userType;
private final String userProperties;
private final String userPropertyMap;
public AuthInfo(String username, String userId, String authToken) {
this(username, userId, authToken, UserType.LEGACY);
}
public AuthInfo(String username, String userId, String authToken, UserType userType) {
this(username, userId, authToken, userType, "{}");
}
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties) {
this(username, userId, authToken, userType, userProperties, "{}");
}
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) {
this.username = username;
this.userId = userId;
this.authToken = authToken;
this.userType = userType;
this.userProperties = userProperties;
this.userPropertyMap = userPropertyMap;
}
public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) {
this(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), authToken, userType, userProperties);
}
public String getUsername() {
return username;
}
public String getUserId() {
return userId;
}
public String getAuthToken() {
return authToken;
}
public UserType getUserType() {
return userType;
}
/**
* Properties of this user.
* Don't know the difference between user properties and user property map.
*
* @return the user property map in JSON.
*/
public String getUserProperties() {
return userProperties;
}
/**
* Properties of this user.
* Don't know the difference between user properties and user property map.
*
* @return the user property map in JSON.
*/
public String getUserPropertyMap() {
return userPropertyMap;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
/**
*
* @author huangyuhui
*/
public class AuthenticationException extends Exception {
public AuthenticationException() {
super();
}
public AuthenticationException(String message) {
super(message);
}
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import java.net.Proxy;
import java.util.Map;
import java.util.Objects;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
/**
*
* @author huang
*/
public class OfflineAccount extends Account {
private final String username;
private final String uuid;
OfflineAccount(String username, String uuid) {
Objects.requireNonNull(username);
Objects.requireNonNull(uuid);
this.username = username;
this.uuid = uuid;
if (StringUtils.isBlank(username))
throw new IllegalArgumentException("Username cannot be blank");
}
public String getUuid() {
return uuid;
}
@Override
public String getUsername() {
return username;
}
@Override
public AuthInfo logIn(Proxy proxy) throws AuthenticationException {
if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid))
throw new AuthenticationException("Username cannot be empty");
return new AuthInfo(username, uuid, uuid);
}
@Override
public void logOut() {
// Offline account need not log out.
}
@Override
public Map<Object, Object> toStorage() {
return Lang.mapOf(
new Pair<>("uuid", uuid),
new Pair<>("username", username)
);
}
@Override
public String toString() {
return "OfflineAccount[username=" + username + ", uuid=" + uuid + "]";
}
}

View File

@@ -0,0 +1,55 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import java.util.Map;
import org.jackhuang.hmcl.util.DigestUtils;
/**
*
* @author huangyuhui
*/
public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
public static final OfflineAccountFactory INSTANCE = new OfflineAccountFactory();
private OfflineAccountFactory() {
}
@Override
public OfflineAccount fromUsername(String username, String password) {
return new OfflineAccount(username, getUUIDFromUserName(username));
}
@Override
public OfflineAccount fromStorage(Map<Object, Object> storage) {
Object username = storage.get("username");
if (username == null || !(username instanceof String))
throw new IllegalStateException("Offline account configuration malformed.");
Object uuid = storage.get("uuid");
if (uuid == null || !(uuid instanceof String))
uuid = getUUIDFromUserName((String) username);
return new OfflineAccount((String) username, (String) uuid);
}
private static String getUUIDFromUserName(String username) {
return DigestUtils.md5Hex(username);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author huangyuhui
*/
public enum UserType {
LEGACY,
MOJANG;
public static UserType fromName(String name) {
return BY_NAME.get(name.toLowerCase());
}
public static UserType fromLegacy(boolean isLegacy) {
return isLegacy ? LEGACY : MOJANG;
}
static {
HashMap<String, UserType> byName = new HashMap<>();
for (UserType type : values())
byName.put(type.name().toLowerCase(), type);
BY_NAME = Collections.unmodifiableMap(byName);
}
public static final Map<String, UserType> BY_NAME;
}

View File

@@ -0,0 +1,61 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import java.util.Map;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
/**
*
* @author huangyuhui
*/
public final class AuthenticationRequest {
/**
* The user name of Minecraft account.
*/
private final String username;
/**
* The password of Minecraft account.
*/
private final String password;
/**
* The client token of this game.
*/
private final String clientToken;
private final Map<String, Object> agent = Lang.mapOf(
new Pair("name", "minecraft"),
new Pair("version", 1));
private final boolean requestUser = true;
public AuthenticationRequest(String username, String password, String clientToken) {
this.username = username;
this.password = password;
this.clientToken = clientToken;
}
public String getUsername() {
return username;
}
public String getClientToken() {
return clientToken;
}
public Map<String, Object> getAgent() {
return agent;
}
public boolean isRequestUser() {
return requestUser;
}
}

View File

@@ -0,0 +1,102 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.UUID;
/**
*
* @author huang
*/
public final class GameProfile {
private final UUID id;
private final String name;
private final PropertyMap properties;
private final boolean legacy;
public GameProfile() {
this(null, null);
}
public GameProfile(UUID id, String name) {
this(id, name, new PropertyMap(), false);
}
public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) {
this.id = id;
this.name = name;
this.properties = properties;
this.legacy = legacy;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public PropertyMap getProperties() {
return properties;
}
public boolean isLegacy() {
return legacy;
}
public static class Serializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
public static final Serializer INSTANCE = new Serializer();
private Serializer() {
}
@Override
public JsonElement serialize(GameProfile src, Type type, JsonSerializationContext context) {
JsonObject result = new JsonObject();
if (src.getId() != null)
result.add("id", context.serialize(src.getId()));
if (src.getName() != null)
result.addProperty("name", src.getName());
return result;
}
@Override
public GameProfile deserialize(JsonElement je, Type type, JsonDeserializationContext context) throws JsonParseException {
if (!(je instanceof JsonObject))
throw new JsonParseException("The json element is not a JsonObject.");
JsonObject json = (JsonObject) je;
UUID id = json.has("id") ? context.deserialize(json.get("id"), UUID.class) : null;
String name = json.has("name") ? json.getAsJsonPrimitive("name").getAsString() : null;
return new GameProfile(id, name);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.AuthenticationException;
/**
*
* @author huangyuhui
*/
public final class InvalidCredentialsException extends AuthenticationException {
private final YggdrasilAccount account;
public InvalidCredentialsException(YggdrasilAccount account) {
this.account = account;
}
public YggdrasilAccount getAccount() {
return account;
}
}

View File

@@ -15,20 +15,24 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth
package org.jackhuang.hmcl.auth.yggdrasil;
enum class UserType() {
LEGACY,
MOJANG;
import org.jackhuang.hmcl.auth.AuthenticationException;
companion object {
/**
*
* @author huangyuhui
*/
public class InvalidTokenException extends AuthenticationException {
fun fromName(name: String) = BY_NAME[name.toLowerCase()]
fun fromLegacy(isLegacy: Boolean) = if (isLegacy) LEGACY else MOJANG
private YggdrasilAccount account;
private val BY_NAME = HashMap<String, UserType>().apply {
for (type in values())
this[type.name.toLowerCase()] = type
}
public InvalidTokenException(YggdrasilAccount account) {
super();
this.account = account;
}
}
public YggdrasilAccount getAccount() {
return account;
}
}

View File

@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,12 +15,23 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.task
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.AutoTypingMap
public class Property {
internal class SimpleTask @JvmOverloads constructor(private val runnable: (AutoTypingMap<String>) -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
override fun execute() {
runnable(variables!!)
private final String name;
private final String value;
public Property(String name, String value) {
this.name = name;
this.value = value;
}
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jackhuang.hmcl.util.Lang;
public final class PropertyMap extends HashMap<String, Property> {
public List<Map<String, String>> toList() {
List<Map<String, String>> properties = new ArrayList<>();
for (Property profileProperty : values()) {
Map<String, String> property = new HashMap<>();
property.put("name", profileProperty.getName());
property.put("value", profileProperty.getValue());
properties.add(property);
}
return properties;
}
public void fromList(List<?> list) {
for (Object propertyMap : list) {
if (!(propertyMap instanceof Map<?, ?>))
continue;
Optional<String> name = Lang.get((Map<?, ?>) propertyMap, "name", String.class);
Optional<String> value = Lang.get((Map<?, ?>) propertyMap, "value", String.class);
if (name.isPresent() && value.isPresent())
put(name.get(), new Property(name.get(), value.get()));
}
}
public static class Serializer implements JsonSerializer<PropertyMap>, JsonDeserializer<PropertyMap> {
public static final Serializer INSTANCE = new Serializer();
private Serializer() {
}
@Override
public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
PropertyMap result = new PropertyMap();
if (json instanceof JsonObject) {
for (Map.Entry<String, JsonElement> entry : ((JsonObject) json).entrySet())
if (entry.getValue() instanceof JsonArray)
for (JsonElement element : (JsonArray) entry.getValue())
result.put(entry.getKey(), new Property(entry.getKey(), element.getAsString()));
} else if ((json instanceof JsonArray))
for (JsonElement element : (JsonArray) json)
if ((element instanceof JsonObject)) {
JsonObject object = (JsonObject) element;
String name = object.getAsJsonPrimitive("name").getAsString();
String value = object.getAsJsonPrimitive("value").getAsString();
result.put(name, new Property(name, value));
}
return result;
}
@Override
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
JsonArray result = new JsonArray();
for (Property property : src.values()) {
JsonObject object = new JsonObject();
object.addProperty("name", property.getName());
object.addProperty("value", property.getValue());
result.add(object);
}
return result;
}
}
public static class LegacySerializer
implements JsonSerializer<PropertyMap> {
@Override
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
for (PropertyMap.Entry<String, Property> entry : src.entrySet()) {
JsonArray values = new JsonArray();
values.add(new JsonPrimitive(entry.getValue().getValue()));
result.add(entry.getKey(), values);
}
return result;
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
/**
*
* @author huang
*/
public final class RefreshRequest {
private final String accessToken;
private final String clientToken;
private final GameProfile selectedProfile;
private final boolean requestUser;
public RefreshRequest(String accessToken, String clientToken) {
this(accessToken, clientToken, null);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) {
this(accessToken, clientToken, selectedProfile, true);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) {
this.accessToken = accessToken;
this.clientToken = clientToken;
this.selectedProfile = selectedProfile;
this.requestUser = requestUser;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
public boolean isRequestUser() {
return requestUser;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
/**
*
* @author huangyuhui
*/
public final class Response {
private final String accessToken;
private final String clientToken;
private final GameProfile selectedProfile;
private final GameProfile[] availableProfiles;
private final User user;
private final String error;
private final String errorMessage;
private final String cause;
public Response() {
this(null, null, null, null, null, null, null, null);
}
public Response(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) {
this.accessToken = accessToken;
this.clientToken = clientToken;
this.selectedProfile = selectedProfile;
this.availableProfiles = availableProfiles;
this.user = user;
this.error = error;
this.errorMessage = errorMessage;
this.cause = cause;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
public GameProfile[] getAvailableProfiles() {
return availableProfiles;
}
public User getUser() {
return user;
}
public String getError() {
return error;
}
public String getErrorMessage() {
return errorMessage;
}
public String getCause() {
return cause;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.Validation;
/**
*
* @author huang
*/
public final class User implements Validation {
private final String id;
private final PropertyMap properties;
public User(String id) {
this(id, null);
}
public User(String id, PropertyMap properties) {
this.id = id;
this.properties = properties;
}
public String getId() {
return id;
}
public PropertyMap getProperties() {
return properties;
}
@Override
public void validate() throws JsonParseException {
if (StringUtils.isBlank(id))
throw new JsonParseException("User id cannot be empty.");
}
}

View File

@@ -15,19 +15,28 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.task
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.AutoTypingMap
import java.util.concurrent.Callable
/**
*
* @author huangyuhui
*/
public final class ValidateRequest {
internal class TaskCallable<V>(override val id: String, private val callable: Callable<V>) : TaskResult<V>() {
override fun execute() {
result = callable.call()
private final String accessToken;
private final String clientToken;
public ValidateRequest(String accessToken, String clientToken) {
this.accessToken = accessToken;
this.clientToken = clientToken;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
}
internal class TaskCallable2<V>(override val id: String, private val callable: (AutoTypingMap<String>) -> V) : TaskResult<V>() {
override fun execute() {
result = callable(variables!!)
}
}

View File

@@ -0,0 +1,274 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.UserType;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
/**
*
* @author huang
*/
public final class YggdrasilAccount extends Account {
private final String username;
private String password;
private String userId;
private String accessToken = null;
private String clientToken = randomToken();
private boolean isOnline = false;
private PropertyMap userProperties = new PropertyMap();
private GameProfile selectedProfile = null;
private GameProfile[] profiles;
private UserType userType = UserType.LEGACY;
public YggdrasilAccount(String username) {
this.username = username;
}
@Override
public String getUsername() {
return username;
}
void setPassword(String password) {
this.password = password;
}
public String getUserId() {
return userId;
}
void setUserId(String userId) {
this.userId = userId;
}
void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
String getClientToken() {
return clientToken;
}
void setClientToken(String clientToken) {
this.clientToken = clientToken;
}
PropertyMap getUserProperties() {
return userProperties;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
void setSelectedProfile(GameProfile selectedProfile) {
this.selectedProfile = selectedProfile;
}
public boolean isLoggedIn() {
return StringUtils.isNotBlank(accessToken);
}
public boolean canPlayOnline() {
return isLoggedIn() && selectedProfile != null && isOnline;
}
public boolean canLogIn() {
return !canPlayOnline() && StringUtils.isNotBlank(username)
&& (StringUtils.isNotBlank(password) || StringUtils.isNotBlank(accessToken));
}
@Override
public AuthInfo logIn(Proxy proxy) throws AuthenticationException {
if (canPlayOnline())
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
else {
logIn0(proxy);
if (!isLoggedIn())
throw new AuthenticationException("Wrong password for account " + username);
if (selectedProfile == null)
// TODO: multi-available-profiles support
throw new UnsupportedOperationException("Do not support multi-available-profiles account yet.");
else
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
}
}
private void logIn0(Proxy proxy) throws AuthenticationException {
if (StringUtils.isNotBlank(accessToken)) {
if (StringUtils.isBlank(userId))
if (StringUtils.isNotBlank(username))
userId = username;
else
throw new AuthenticationException("Invalid uuid and username");
if (checkTokenValidity(proxy)) {
isOnline = true;
return;
}
logIn1(ROUTE_REFRESH, new RefreshRequest(accessToken, clientToken), proxy);
} else if (StringUtils.isNotBlank(password))
logIn1(ROUTE_AUTHENTICATE, new AuthenticationRequest(username, password, clientToken), proxy);
else
throw new AuthenticationException("Password cannot be blank");
}
private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException {
Response response = makeRequest(url, input, proxy);
if (response == null || !clientToken.equals(response.getClientToken()))
throw new AuthenticationException("Client token changed");
if (response.getSelectedProfile() != null)
userType = UserType.fromLegacy(response.getSelectedProfile().isLegacy());
else if (response.getAvailableProfiles() != null && response.getAvailableProfiles().length > 0)
userType = UserType.fromLegacy(response.getAvailableProfiles()[0].isLegacy());
User user = response.getUser();
if (user == null || user.getId() == null)
userId = null;
else
userId = user.getId();
isOnline = true;
profiles = response.getAvailableProfiles();
selectedProfile = response.getSelectedProfile();
userProperties.clear();
accessToken = response.getAccessToken();
if (user != null && user.getProperties() != null)
userProperties.putAll(user.getProperties());
}
@Override
public void logOut() {
password = null;
userId = null;
accessToken = null;
isOnline = false;
userProperties.clear();
profiles = null;
selectedProfile = null;
}
@Override
public Map<Object, Object> toStorage() {
HashMap<Object, Object> result = new HashMap<>();
result.put(STORAGE_KEY_USER_NAME, getUsername());
result.put(STORAGE_KEY_CLIENT_TOKEN, getClientToken());
if (getUserId() != null)
result.put(STORAGE_KEY_USER_ID, getUserId());
if (!userProperties.isEmpty())
result.put(STORAGE_KEY_USER_PROPERTIES, userProperties.toList());
GameProfile profile = selectedProfile;
if (profile != null && profile.getName() != null && profile.getId() != null) {
result.put(STORAGE_KEY_PROFILE_NAME, profile.getName());
result.put(STORAGE_KEY_PROFILE_ID, profile.getId());
if (!profile.getProperties().isEmpty())
result.put(STORAGE_KEY_PROFILE_PROPERTIES, profile.getProperties().toList());
}
if (StringUtils.isNotBlank(accessToken))
result.put(STORAGE_KEY_ACCESS_TOKEN, accessToken);
return result;
}
private Response makeRequest(URL url, Object input, Proxy proxy) throws AuthenticationException {
try {
String jsonResult = input == null ? NetworkUtils.doGet(url, proxy) : NetworkUtils.doPost(url, GSON.toJson(input), "application/json", proxy);
Response response = GSON.fromJson(jsonResult, Response.class);
if (response == null)
return null;
if (!StringUtils.isBlank(response.getError())) {
if (response.getErrorMessage() != null)
if (response.getErrorMessage().contains("Invalid credentials"))
throw new InvalidCredentialsException(this);
else if (response.getErrorMessage().contains("Invalid token"))
throw new InvalidTokenException(this);
throw new AuthenticationException(response.getError() + ": " + response.getErrorMessage());
}
return response;
} catch (IOException e) {
throw new AuthenticationException("Unable to connect to authentication server", e);
} catch (JsonParseException e) {
throw new AuthenticationException("Unable to parse server response", e);
}
}
private boolean checkTokenValidity(Proxy proxy) {
if (accessToken == null)
return false;
try {
makeRequest(ROUTE_VALIDATE, new ValidateRequest(accessToken, clientToken), proxy);
return true;
} catch (AuthenticationException e) {
return false;
}
}
@Override
public String toString() {
return "YggdrasilAccount[username=" + getUsername() + "]";
}
private static final String BASE_URL = "https://authserver.mojang.com/";
private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate");
private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh");
private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate");
static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
static final String STORAGE_KEY_PROFILE_NAME = "displayName";
static final String STORAGE_KEY_PROFILE_ID = "uuid";
static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties";
static final String STORAGE_KEY_USER_NAME = "username";
static final String STORAGE_KEY_USER_ID = "userid";
static final String STORAGE_KEY_USER_PROPERTIES = "userProperties";
static final String STORAGE_KEY_CLIENT_TOKEN = "clientToken";
public static String randomToken() {
return UUIDTypeAdapter.fromUUID(UUID.randomUUID());
}
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE)
.registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE)
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
.create();
}

View File

@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.util.Lang;
import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
/**
*
* @author huangyuhui
*/
public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory();
private YggdrasilAccountFactory() {
}
@Override
public YggdrasilAccount fromUsername(String username, String password) {
YggdrasilAccount account = new YggdrasilAccount(username);
account.setPassword(password);
return account;
}
@Override
public YggdrasilAccount fromStorage(Map<Object, Object> storage) {
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
YggdrasilAccount account = new YggdrasilAccount(username);
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username));
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null));
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN)));
Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class)
.ifPresent(account.getUserProperties()::fromList);
Optional<String> profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class);
Optional<String> profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class);
GameProfile profile = null;
if (profileId.isPresent() && profileName.isPresent()) {
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get());
Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class)
.ifPresent(profile.getProperties()::fromList);
}
account.setSelectedProfile(profile);
return account;
}
}

View File

@@ -15,14 +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;
abstract class AbstractDependencyManager
: DependencyManager {
abstract val downloadProvider: DownloadProvider
/**
*
* @author huangyuhui
*/
public abstract class AbstractDependencyManager implements DependencyManager {
override fun getVersionList(id: String): VersionList<*> {
return downloadProvider.getVersionListById(id)
public abstract DownloadProvider getDownloadProvider();
@Override
public VersionList<?> getVersionList(String id) {
return getDownloadProvider().getVersionListById(id);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
import org.jackhuang.hmcl.download.game.GameVersionList;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
/**
*
* @author huang
*/
public class BMCLAPIDownloadProvider implements DownloadProvider {
public static final BMCLAPIDownloadProvider INSTANCE = new BMCLAPIDownloadProvider();
private BMCLAPIDownloadProvider() {
}
@Override
public String getLibraryBaseURL() {
return "http://bmclapi2.bangbang93.com/libraries/";
}
@Override
public String getVersionListURL() {
return "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
}
@Override
public String getVersionBaseURL() {
return "http://bmclapi2.bangbang93.com/versions/";
}
@Override
public String getAssetIndexBaseURL() {
return "http://bmclapi2.bangbang93.com/indexes/";
}
@Override
public String getAssetBaseURL() {
return "http://bmclapi2.bangbang93.com/assets/";
}
@Override
public VersionList<?> getVersionListById(String id) {
switch (id) {
case "game":
return GameVersionList.INSTANCE;
case "forge":
return ForgeVersionList.INSTANCE;
case "liteloader":
return LiteLoaderVersionList.INSTANCE;
case "optifine":
return OptiFineBMCLVersionList.INSTANCE;
default:
throw new IllegalArgumentException("Unrecognized version list id: " + id);
}
}
@Override
public String injectURL(String baseURL) {
return baseURL
.replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
}
}

View File

@@ -0,0 +1,100 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
import java.net.Proxy;
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;
import org.jackhuang.hmcl.task.Task;
/**
* Note: This class has no state.
*
* @author huangyuhui
*/
public class DefaultDependencyManager extends AbstractDependencyManager {
private final DefaultGameRepository repository;
private final DownloadProvider downloadProvider;
private final Proxy proxy;
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider) {
this(repository, downloadProvider, Proxy.NO_PROXY);
}
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, Proxy proxy) {
this.repository = repository;
this.downloadProvider = downloadProvider;
this.proxy = proxy;
}
@Override
public DefaultGameRepository getGameRepository() {
return repository;
}
@Override
public DownloadProvider getDownloadProvider() {
return downloadProvider;
}
@Override
public Proxy getProxy() {
return proxy;
}
@Override
public GameBuilder gameBuilder() {
return new DefaultGameBuilder(this);
}
@Override
public Task checkGameCompletionAsync(Version version) {
return new ParallelTask(
new GameAssetDownloadTask(this, version),
new GameLoggingDownloadTask(this, version),
new GameLibrariesTask(this, version)
);
}
@Override
public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
switch (libraryId) {
case "forge":
return new ForgeInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
case "liteloader":
return new LiteLoaderInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
case "optifine":
return new OptiFineInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
default:
throw new IllegalArgumentException("Library id " + libraryId + " is unrecognized.");
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
import java.util.function.Function;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask;
import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.Constants;
/**
*
* @author huangyuhui
*/
public class DefaultGameBuilder extends GameBuilder {
private final DefaultDependencyManager dependencyManager;
private final DownloadProvider downloadProvider;
public DefaultGameBuilder(DefaultDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
this.downloadProvider = dependencyManager.getDownloadProvider();
}
@Override
public Task buildAsync() {
return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> {
Version version = Constants.GSON.fromJson(variables.<String>get(VersionJsonDownloadTask.ID), Version.class);
variables.set("version", version);
version = version.setId(name).setJar(null);
Task result = new ParallelTask(
new GameAssetDownloadTask(dependencyManager, version),
new GameLoggingDownloadTask(dependencyManager, version),
new GameDownloadTask(dependencyManager, version),
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version)); // using [with] because download failure here are tolerant.
if (toolVersions.containsKey("forge"))
result = result.then(libraryTaskHelper(gameVersion, "forge"));
if (toolVersions.containsKey("liteloader"))
result = result.then(libraryTaskHelper(gameVersion, "liteloader"));
if (toolVersions.containsKey("optifine"))
result = result.then(libraryTaskHelper(gameVersion, "optifine"));
return result;
});
}
private Function<AutoTypingMap<String>, Task> libraryTaskHelper(String gameVersion, String libraryId) {
return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId));
}
}

View File

@@ -15,27 +15,30 @@
* 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;
import org.jackhuang.hmcl.game.GameRepository
import org.jackhuang.hmcl.game.Version
import org.jackhuang.hmcl.task.Task
import java.net.Proxy
import java.net.Proxy;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
/**
* Do everything that will connect to Internet.
* Downloading Minecraft files.
*
* @author huangyuhui
*/
interface DependencyManager {
public interface DependencyManager {
/**
* The relied game repository.
*/
val repository: GameRepository
GameRepository getGameRepository();
/**
* The proxy that all network operations should go through.
*/
val proxy: Proxy
Proxy getProxy();
/**
* Check if the game is complete.
@@ -43,12 +46,12 @@ interface DependencyManager {
*
* @return the task to check game completion.
*/
fun checkGameCompletionAsync(version: Version): Task
Task checkGameCompletionAsync(Version version);
/**
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.
*/
fun gameBuilder(): GameBuilder
GameBuilder gameBuilder();
/**
* Install a library to a version.
@@ -60,7 +63,7 @@ interface DependencyManager {
* @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
Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion);
/**
* Get registered version list.
@@ -68,5 +71,5 @@ interface DependencyManager {
* @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.
*/
fun getVersionList(id: String): VersionList<*>
}
VersionList<?> getVersionList(String id);
}

View File

@@ -15,17 +15,24 @@
* 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;
/**
* The service provider that provides Minecraft online file downloads.
*
* @author huangyuhui
*/
interface DownloadProvider {
val libraryBaseURL: String
val versionListURL: String
val versionBaseURL: String
val assetIndexBaseURL: String
val assetBaseURL: String
public interface DownloadProvider {
String getLibraryBaseURL();
String getVersionListURL();
String getVersionBaseURL();
String getAssetIndexBaseURL();
String getAssetBaseURL();
/**
* Inject into original URL provided by Mojang and Forge.
@@ -36,12 +43,13 @@ interface 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.
*/
fun injectURL(baseURL: String): String
String injectURL(String baseURL);
/**
* 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
*/
fun getVersionListById(id: String): VersionList<*>
}
VersionList<?> getVersionListById(String id);
}

View File

@@ -15,45 +15,57 @@
* 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;
import org.jackhuang.hmcl.task.Task
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
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>()
public abstract class GameBuilder {
protected String name = "";
protected String gameVersion = "";
protected Map<String, String> toolVersions = new HashMap<>();
public String getName() {
return name;
}
/**
* 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
public GameBuilder name(String name) {
this.name = Objects.requireNonNull(name);
return this;
}
fun gameVersion(version: String): GameBuilder {
gameVersion = version
return this
public GameBuilder gameVersion(String version) {
this.gameVersion = Objects.requireNonNull(version);
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
public GameBuilder version(String id, String version) {
if ("game".equals(id))
gameVersion(version);
else
toolVersions.put(id, version);
return this;
}
/**
* @return the task that can build thw whole Minecraft environment
*/
abstract fun buildAsync(): Task
}
public abstract Task buildAsync();
}

View File

@@ -0,0 +1,84 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
import 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;
/**
* @see {@link http://wiki.vg}
* @author huangyuhui
*/
public final class MojangDownloadProvider implements DownloadProvider {
public static final MojangDownloadProvider INSTANCE = new MojangDownloadProvider();
private MojangDownloadProvider() {
}
@Override
public String getLibraryBaseURL() {
return "https://libraries.minecraft.net/";
}
@Override
public String getVersionListURL() {
return "https://launchermeta.mojang.com/mc/game/version_manifest.json";
}
@Override
public String getVersionBaseURL() {
return "http://s3.amazonaws.com/Minecraft.Download/versions/";
}
@Override
public String getAssetIndexBaseURL() {
return "http://s3.amazonaws.com/Minecraft.Download/indexes/";
}
@Override
public String getAssetBaseURL() {
return "http://resources.download.minecraft.net/";
}
@Override
public VersionList<?> getVersionListById(String id) {
switch (id) {
case "game":
return GameVersionList.INSTANCE;
case "forge":
return ForgeVersionList.INSTANCE;
case "liteloader":
return LiteLoaderVersionList.INSTANCE;
case "optifine":
return OptiFineVersionList.INSTANCE;
default:
throw new IllegalArgumentException("Unrecognized version list id: " + id);
}
}
@Override
public String injectURL(String baseURL) {
if (baseURL.contains("net/minecraftforge/forge"))
return baseURL;
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
}
}

View File

@@ -0,0 +1,96 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
import java.util.Comparator;
import java.util.Objects;
import org.jackhuang.hmcl.util.VersionNumber;
/**
* The remote version.
*
* @author huangyuhui
*/
public final class RemoteVersion<T> implements Comparable<RemoteVersion<T>> {
private final String gameVersion;
private final String selfVersion;
private final String url;
private final T tag;
/**
* Constructor.
*
* @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.
*/
public RemoteVersion(String gameVersion, String selfVersion, String url, T tag) {
this.gameVersion = Objects.requireNonNull(gameVersion);
this.selfVersion = Objects.requireNonNull(selfVersion);
this.url = Objects.requireNonNull(url);
this.tag = tag;
}
public String getGameVersion() {
return gameVersion;
}
public String getSelfVersion() {
return selfVersion;
}
public T getTag() {
return tag;
}
public String getUrl() {
return url;
}
@Override
public boolean equals(Object obj) {
return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);
}
@Override
public int hashCode() {
return selfVersion.hashCode();
}
@Override
public int compareTo(RemoteVersion<T> o) {
// newer versions are smaller than older versions
return -selfVersion.compareTo(o.selfVersion);
}
public static class RemoteVersionComparator implements Comparator<RemoteVersion<?>> {
public static final RemoteVersionComparator INSTANCE = new RemoteVersionComparator();
private RemoteVersionComparator() {
}
@Override
public int compare(RemoteVersion<?> o1, RemoteVersion<?> o2) {
return -VersionNumber.asVersion(o1.selfVersion).compareTo(VersionNumber.asVersion(o2.selfVersion));
}
}
}

View File

@@ -15,39 +15,48 @@
* 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;
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.util.SimpleMultimap
import java.util.*
import kotlin.collections.HashMap
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Optional;
import java.util.TreeSet;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.SimpleMultimap;
/**
* The remote version list.
* @param T The type of RemoteVersion<T>, the type of tags.
*
* @param T The type of {@code RemoteVersion<T>}, the type of tags.
*
* @author huangyuhui
*/
abstract class VersionList<T> {
public abstract class VersionList<T> {
/**
* the remote version list.
* key: game version.
* values: corresponding remote versions.
*/
protected val versions = SimpleMultimap<String, RemoteVersion<T>>(::HashMap, ::TreeSet)
protected final SimpleMultimap<String, RemoteVersion<T>> versions = new SimpleMultimap<String, RemoteVersion<T>>(HashMap::new, TreeSet::new);
/**
* True if the version list has been loaded.
*/
val loaded = versions.isNotEmpty
public boolean isLoaded() {
return !versions.isEmpty();
}
/**
* @param downloadProvider DownloadProvider
* @return the task to reload the remote version list.
*/
abstract fun refreshAsync(downloadProvider: DownloadProvider): Task
public abstract Task refreshAsync(DownloadProvider downloadProvider);
private fun getVersionsImpl(gameVersion: String): Collection<RemoteVersion<T>> {
val ans = versions[gameVersion]
return if (ans.isEmpty()) versions.values else ans
private Collection<RemoteVersion<T>> getVersionsImpl(String gameVersion) {
Collection<RemoteVersion<T>> ans = versions.get(gameVersion);
return ans.isEmpty() ? versions.values() : ans;
}
/**
@@ -56,8 +65,8 @@ abstract class VersionList<T> {
* @param gameVersion the Minecraft version that remote versions belong to
* @return the collection of specific remote versions
*/
fun getVersions(gameVersion: String): Collection<RemoteVersion<T>> {
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion))
public final Collection<RemoteVersion<T>> getVersions(String gameVersion) {
return Collections.unmodifiableCollection(getVersionsImpl(gameVersion));
}
/**
@@ -67,13 +76,11 @@ abstract class VersionList<T> {
* @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 {
if (it.selfVersion == remoteVersion)
result = it
}
return result
public final Optional<RemoteVersion<T>> getVersion(String gameVersion, String remoteVersion) {
RemoteVersion<T> result = null;
for (RemoteVersion<T> it : versions.get(gameVersion))
if (remoteVersion.equals(it.getSelfVersion()))
result = it;
return Optional.ofNullable(result);
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.SimpleVersionProvider;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
*
* @author huangyuhui
*/
public final class ForgeInstallTask extends TaskResult<Version> {
private final DefaultDependencyManager dependencyManager;
private final String gameVersion;
private final Version version;
private final String remoteVersion;
private final VersionList<?> forgeVersionList;
private final File installer = new File("forge-installer.jar").getAbsoluteFile();
private RemoteVersion<?> remote;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private Task downloadFileTask() {
remote = forgeVersionList.getVersion(gameVersion, remoteVersion)
.orElseThrow(() -> new IllegalArgumentException("Remote forge version " + gameVersion + ", " + remoteVersion + " not found"));
return new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer);
}
public ForgeInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
this.dependencyManager = dependencyManager;
this.gameVersion = gameVersion;
this.version = version;
this.remoteVersion = remoteVersion;
forgeVersionList = dependencyManager.getVersionList("forge");
if (!forgeVersionList.isLoaded())
dependents.add(forgeVersionList.refreshAsync(dependencyManager.getDownloadProvider())
.then(s -> downloadFileTask()));
else
dependents.add(downloadFileTask());
}
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public String getId() {
return "version";
}
@Override
public boolean isRelyingOnDependencies() {
return false;
}
@Override
public void execute() throws Exception {
try (ZipFile zipFile = new ZipFile(installer)) {
InputStream stream = zipFile.getInputStream(zipFile.getEntry("install_profile.json"));
if (stream == null)
throw new IOException("Malformed forge installer file, install_profile.json does not exist.");
String json = IOUtils.readFullyAsString(stream);
InstallProfile installProfile = Constants.GSON.fromJson(json, InstallProfile.class);
if (installProfile == null)
throw new IOException("Malformed forge installer file, install_profile.json does not exist.");
// unpack the universal jar in the installer file.
Library forgeLibrary = Library.fromName(installProfile.getInstall().getPath());
File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);
if (!FileUtils.makeFile(forgeFile))
throw new IOException("Cannot make directory " + forgeFile.getParent());
ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath());
try (InputStream is = zipFile.getInputStream(forgeEntry); OutputStream os = new FileOutputStream(forgeFile)) {
IOUtils.copyTo(is, os);
}
// resolve the version
SimpleVersionProvider provider = new SimpleVersionProvider();
provider.addVersion(version);
setResult(installProfile.getVersionInfo()
.setInheritsFrom(version.getId())
.resolve(provider)
.setId(version.getId()).setLogging(Collections.EMPTY_MAP));
dependencies.add(new GameLibrariesTask(dependencyManager, installProfile.getVersionInfo()));
}
if (!installer.delete())
throw new IOException("Unable to delete installer file" + installer);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Validation;
/**
*
* @author huangyuhui
*/
@Immutable
public final class ForgeVersion implements Validation {
private final String branch;
private final String mcversion;
private final String jobver;
private final String version;
private final int build;
private final double modified;
private final String[][] files;
public ForgeVersion() {
this(null, null, null, null, 0, 0, null);
}
public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, double modified, String[][] files) {
this.branch = branch;
this.mcversion = mcversion;
this.jobver = jobver;
this.version = version;
this.build = build;
this.modified = modified;
this.files = files;
}
public String getBranch() {
return branch;
}
public String getGameVersion() {
return mcversion;
}
public String getJobver() {
return jobver;
}
public String getVersion() {
return version;
}
public int getBuild() {
return build;
}
public double getModified() {
return modified;
}
public String[][] getFiles() {
return files;
}
@Override
public void validate() throws JsonParseException {
if (files == null)
throw new JsonParseException("ForgeVersion files cannot be null");
if (version == null)
throw new JsonParseException("ForgeVersion version cannot be null");
if (mcversion == null)
throw new JsonParseException("ForgeVersion mcversion cannot be null");
}
}

View File

@@ -0,0 +1,93 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.VersionNumber;
/**
*
* @author huangyuhui
*/
public final class ForgeVersionList extends VersionList<Void> {
public static final ForgeVersionList INSTANCE = new ForgeVersionList();
private ForgeVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
final List<Task> dependents = Collections.singletonList(task);
return new Task() {
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public void execute() throws Exception {
ForgeVersionRoot root = Constants.GSON.fromJson(task.getResult(), ForgeVersionRoot.class);
if (root == null)
return;
versions.clear();
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
String gameVersion = VersionNumber.parseVersion(entry.getKey());
if (gameVersion == null)
continue;
for (int v : entry.getValue()) {
ForgeVersion version = root.getNumber().get(v);
if (version == null)
continue;
String jar = null;
for (String[] file : version.getFiles())
if (file.length > 1 && "installer".equals(file[1])) {
String classifier = version.getGameVersion() + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0];
jar = downloadProvider.injectURL(root.getWebPath() + classifier + "/" + fileName);
}
if (jar == null)
continue;
versions.put(gameVersion, new RemoteVersion<>(
version.getGameVersion(), version.getVersion(), jar, null
));
}
}
}
};
}
public static final String FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
}

View File

@@ -0,0 +1,102 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import com.google.gson.JsonParseException;
import java.util.Map;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Validation;
/**
*
* @author huangyuhui
*/
@Immutable
public final class ForgeVersionRoot implements Validation {
private final String artifact;
private final String webpath;
private final String adfly;
private final String homepage;
private final String name;
private final Map<String, int[]> branches;
private final Map<String, int[]> mcversion;
private final Map<String, Integer> promos;
private final Map<Integer, ForgeVersion> number;
public ForgeVersionRoot() {
this(null, null, null, null, null, null, null, null, null);
}
public ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, Map<String, int[]> branches, Map<String, int[]> mcversion, Map<String, Integer> promos, Map<Integer, ForgeVersion> number) {
this.artifact = artifact;
this.webpath = webpath;
this.adfly = adfly;
this.homepage = homepage;
this.name = name;
this.branches = branches;
this.mcversion = mcversion;
this.promos = promos;
this.number = number;
}
public String getArtifact() {
return artifact;
}
public String getWebPath() {
return webpath;
}
public String getAdfly() {
return adfly;
}
public String getHomePage() {
return homepage;
}
public String getName() {
return name;
}
public Map<String, int[]> getBranches() {
return branches;
}
public Map<String, int[]> getGameVersions() {
return mcversion;
}
public Map<String, Integer> getPromos() {
return promos;
}
public Map<Integer, ForgeVersion> getNumber() {
return number;
}
@Override
public void validate() throws JsonParseException {
if (number == null)
throw new JsonParseException("ForgeVersionRoot number cannot be null");
if (mcversion == null)
throw new JsonParseException("ForgeVersionRoot mcversion cannot be null");
}
}

View File

@@ -0,0 +1,91 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class Install {
private final String profileName;
private final String target;
private final String path;
private final String version;
private final String filePath;
private final String welcome;
private final String minecraft;
private final String mirrorList;
private final String logo;
public Install() {
this(null, null, null, null, null, null, null, null, null);
}
public Install(String profileName, String target, String path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
this.profileName = profileName;
this.target = target;
this.path = path;
this.version = version;
this.filePath = filePath;
this.welcome = welcome;
this.minecraft = minecraft;
this.mirrorList = mirrorList;
this.logo = logo;
}
public String getProfileName() {
return profileName;
}
public String getTarget() {
return target;
}
public String getPath() {
return path;
}
public String getVersion() {
return version;
}
public String getFilePath() {
return filePath;
}
public String getWelcome() {
return welcome;
}
public String getMinecraft() {
return minecraft;
}
public String getMirrorList() {
return mirrorList;
}
public String getLogo() {
return logo;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.forge;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Validation;
/**
*
* @author huangyuhui
*/
@Immutable
public final class InstallProfile implements Validation {
@SerializedName("install")
private final Install install;
@SerializedName("versionInfo")
private final Version versionInfo;
public InstallProfile(Install install, Version versionInfo) {
this.install = install;
this.versionInfo = versionInfo;
}
public Install getInstall() {
return install;
}
public Version getVersionInfo() {
return versionInfo;
}
@Override
public void validate() throws JsonParseException {
if (install == null)
throw new JsonParseException("InstallProfile install cannot be null");
if (versionInfo == null)
throw new JsonParseException("InstallProfile versionInfo cannot be null");
}
}

View File

@@ -0,0 +1,111 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.AssetIndexInfo;
import org.jackhuang.hmcl.game.AssetObject;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
*
* @author huangyuhui
*/
public final class GameAssetDownloadTask extends Task {
private final AbstractDependencyManager dependencyManager;
private final Version version;
private final GameAssetRefreshTask refreshTask;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.refreshTask = new GameAssetRefreshTask(dependencyManager, version);
this.dependents.add(refreshTask);
}
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
int size = refreshTask.getResult().size();
for (Map.Entry<File, AssetObject> entry : refreshTask.getResult()) {
File file = entry.getKey();
AssetObject assetObject = entry.getValue();
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation();
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) {
Logging.LOG.log(Level.SEVERE, "Unable to create new file {0}, because parent directory cannot be created", file);
continue;
}
if (file.isDirectory())
continue;
boolean flag = true;
int downloaded = 0;
try {
// check the checksum of file to ensure that the file is not need to re-download.
if (file.exists()) {
String sha1 = DigestUtils.sha1Hex(FileUtils.readBytes(file));
if (sha1.equals(assetObject.getHash())) {
++downloaded;
Logging.LOG.finest("File $file has been downloaded successfully, skipped downloading");
updateProgress(downloaded, size);
continue;
}
}
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to get hash code of file " + file, e);
flag = !file.exists();
}
if (flag) {
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), assetObject.getHash());
task.setName(assetObject.getHash());
dependencies.add(task);
}
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.AssetIndexInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
* This task is to download asset index file provided in minecraft.json.
*
* @author huangyuhui
*/
public final class GameAssetIndexDownloadTask extends Task {
private final AbstractDependencyManager dependencyManager;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
AssetIndexInfo assetIndexInfo = version.getAssetIndex();
File assetDir = dependencyManager.getGameRepository().getAssetDirectory(version.getId(), assetIndexInfo.getId());
if (FileUtils.makeDirectory(assetDir))
throw new IOException("Cannot create directory: " + assetDir);
File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())),
assetIndexFile, dependencyManager.getProxy()
));
}
}

View File

@@ -0,0 +1,88 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.AssetIndex;
import org.jackhuang.hmcl.game.AssetIndexInfo;
import org.jackhuang.hmcl.game.AssetObject;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Pair;
/**
* This task is to extract all asset objects described in asset index json.
*
* @author huangyuhui
*/
public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File, AssetObject>>> {
private final AbstractDependencyManager dependencyManager;
private final Version version;
private final AssetIndexInfo assetIndexInfo;
private final File assetIndexFile;
private final List<Task> dependents = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.assetIndexInfo = version.getAssetIndex();
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
if (!assetIndexFile.exists())
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version));
}
@Override
public List<Task> getDependents() {
return dependents;
}
@Override
public String getId() {
return ID;
}
@Override
public void execute() throws Exception {
AssetIndex index = Constants.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class);
List<Pair<File, AssetObject>> res = new LinkedList<>();
int progress = 0;
if (index != null)
for (AssetObject assetObject : index.getObjects().values()) {
res.add(new Pair<>(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
updateProgress(++progress, index.getObjects().size());
}
setResult(res);
}
public static final String ID = "game_asset_refresh_task";
}

View File

@@ -0,0 +1,60 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
*
* @author huangyuhui
*/
public final class GameDownloadTask extends Task {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
public GameDownloadTask(DefaultDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
File jar = dependencyManager.getGameRepository().getVersionJar(version);
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
jar,
dependencyManager.getProxy(),
version.getDownloadInfo().getSha1()
));
}
}

View File

@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.AbstractDependencyManager;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
* This task is to download game libraries.
* This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task).
*
* @author huangyuhui
*/
public final class GameLibrariesTask extends Task {
private final AbstractDependencyManager dependencyManager;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment()) {
File file = dependencyManager.getGameRepository().getLibraryFile(version, library);
if (!file.exists())
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl())),
file, dependencyManager.getProxy(), library.getDownload().getSha1()));
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.game.DownloadType;
import org.jackhuang.hmcl.game.LoggingInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
* This task is to download log4j configuration file provided in minecraft.json.
*
* @author huangyuhui
*/
public final class GameLoggingDownloadTask extends Task {
private final DependencyManager dependencyManager;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
if (version.getLogging() == null || !version.getLogging().containsKey(DownloadType.CLIENT))
return;
LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT);
File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging);
if (!file.exists())
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file, dependencyManager.getProxy()));
}
}

View File

@@ -0,0 +1,52 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class GameRemoteLatestVersions {
@SerializedName("snapshot")
private final String snapshot;
@SerializedName("release")
private final String release;
public GameRemoteLatestVersions() {
this(null, null);
}
public GameRemoteLatestVersions(String snapshot, String release) {
this.snapshot = snapshot;
this.release = release;
}
public String getRelease() {
return release;
}
public String getSnapshot() {
return snapshot;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.Validation;
/**
*
* @author huangyuhui
*/
public final class GameRemoteVersion implements Validation {
@SerializedName("id")
private final String gameVersion;
@SerializedName("time")
private final Date time;
@SerializedName("releaseTime")
private final Date releaseTime;
@SerializedName("type")
private final ReleaseType type;
@SerializedName("url")
private final String url;
public GameRemoteVersion() {
this("", new Date(), new Date(), ReleaseType.UNKNOWN);
}
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type) {
this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json");
}
public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) {
this.gameVersion = gameVersion;
this.time = time;
this.releaseTime = releaseTime;
this.type = type;
this.url = url;
}
public String getGameVersion() {
return gameVersion;
}
public Date getTime() {
return time;
}
public Date getReleaseTime() {
return releaseTime;
}
public ReleaseType getType() {
return type;
}
public String getUrl() {
return url;
}
@Override
public void validate() throws JsonParseException {
if (StringUtils.isBlank(gameVersion))
throw new JsonParseException("GameRemoteVersion id cannot be blank");
if (StringUtils.isBlank(url))
throw new JsonParseException("GameRemoteVersion url cannot be blank");
}
}

View File

@@ -15,27 +15,36 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game
package org.jackhuang.hmcl.download.game;
import com.google.gson.annotations.SerializedName
import org.jackhuang.hmcl.util.Immutable
import org.jackhuang.hmcl.util.Validation
import java.util.Date;
import org.jackhuang.hmcl.game.ReleaseType;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
class LoggingInfo @JvmOverloads constructor(
@SerializedName("file")
val file: IdDownloadInfo = IdDownloadInfo(),
public final class GameRemoteVersionTag {
@SerializedName("argument")
val argument: String = "",
private final ReleaseType type;
private final Date time;
@SerializedName("type")
val type: String = ""
): Validation {
override fun validate() {
file.validate()
require(argument.isNotBlank(), { "LoggingInfo" })
require(type.isNotBlank())
public GameRemoteVersionTag() {
this(ReleaseType.UNKNOWN, new Date());
}
}
public GameRemoteVersionTag(ReleaseType type, Date time) {
this.type = type;
this.time = time;
}
public Date getTime() {
return time;
}
public ReleaseType getType() {
return type;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import com.google.gson.annotations.SerializedName;
import java.util.Collections;
import java.util.List;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class GameRemoteVersions {
@SerializedName("versions")
private final List<GameRemoteVersion> versions;
@SerializedName("latest")
private final GameRemoteLatestVersions latest;
public GameRemoteVersions() {
this(Collections.EMPTY_LIST, null);
}
public GameRemoteVersions(List<GameRemoteVersion> versions, GameRemoteLatestVersions latest) {
this.versions = versions;
this.latest = latest;
}
public GameRemoteLatestVersions getLatest() {
return latest;
}
public List<GameRemoteVersion> getVersions() {
return versions;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.util.Collection;
import java.util.Collections;
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.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
/**
*
* @author huangyuhui
*/
public final class GameVersionList extends VersionList<GameRemoteVersionTag> {
public static final GameVersionList INSTANCE = new GameVersionList();
private GameVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
versions.clear();
GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class);
for (GameRemoteVersion remoteVersion : root.getVersions()) {
String gameVersion = VersionNumber.parseVersion(remoteVersion.getGameVersion());
if (gameVersion == null)
continue;
versions.put(gameVersion, new RemoteVersion<>(
remoteVersion.getGameVersion(),
remoteVersion.getGameVersion(),
remoteVersion.getUrl(),
new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime()))
);
}
}
};
}
}

View File

@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.net.Proxy;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
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.NetworkUtils;
/**
*
* @author huangyuhui
*/
public final class VersionJsonDownloadTask extends Task {
private final String gameVersion;
private final DefaultDependencyManager dependencyManager;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private final VersionList<?> gameVersionList;
public VersionJsonDownloadTask(String gameVersion, DefaultDependencyManager dependencyManager) {
this.gameVersion = gameVersion;
this.dependencyManager = dependencyManager;
this.gameVersionList = dependencyManager.getVersionList("game");
if (!gameVersionList.isLoaded())
dependents.add(gameVersionList.refreshAsync(dependencyManager.getDownloadProvider()));
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public void execute() throws Exception {
RemoteVersion<?> remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find specific version "+gameVersion+" in remote repository"));
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), Proxy.NO_PROXY, ID));
}
public static final String ID = "raw_version_json";
}

View File

@@ -0,0 +1,56 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import java.io.File;
import java.io.IOException;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
/**
* This task is to save the version json.
*
* @author huangyuhui
*/
public final class VersionJsonSaveTask extends Task {
private final DefaultGameRepository repository;
private final Version version;
/**
* Constructor.
*
* @param repository the game repository
* @param version the **resolved** version
*/
public VersionJsonSaveTask(DefaultGameRepository repository, Version version) {
this.repository = repository;
this.version = version;
}
@Override
public void execute() throws Exception {
File json = repository.getVersionJson(version.getId()).getAbsoluteFile();
if (!FileUtils.makeFile(json))
throw new IOException("Cannot create file " + json);
FileUtils.writeText(json, Constants.GSON.toJson(version));
}
}

View File

@@ -0,0 +1,57 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import com.google.gson.annotations.SerializedName;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderBranch {
@SerializedName("libraries")
private final Collection<Library> libraries;
@SerializedName("com.mumfrey:liteloader")
private final Map<String, LiteLoaderVersion> liteLoader;
public LiteLoaderBranch() {
this(Collections.EMPTY_SET, Collections.EMPTY_MAP);
}
public LiteLoaderBranch(Collection<Library> libraries, Map<String, LiteLoaderVersion> liteLoader) {
this.libraries = libraries;
this.liteLoader = liteLoader;
}
public Collection<Library> getLibraries() {
return Collections.unmodifiableCollection(libraries);
}
public Map<String, LiteLoaderVersion> getLiteLoader() {
return Collections.unmodifiableMap(liteLoader);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderGameVersions {
@SerializedName("repo")
private final LiteLoaderRepository repoitory;
@SerializedName("artefacts")
private final LiteLoaderBranch artifacts;
@SerializedName("snapshots")
private final LiteLoaderBranch snapshots;
public LiteLoaderGameVersions() {
this(null, null, null);
}
public LiteLoaderGameVersions(LiteLoaderRepository repoitory, LiteLoaderBranch artifacts, LiteLoaderBranch snapshots) {
this.repoitory = repoitory;
this.artifacts = artifacts;
this.snapshots = snapshots;
}
public LiteLoaderRepository getRepoitory() {
return repoitory;
}
public LiteLoaderBranch getArtifacts() {
return artifacts;
}
public LiteLoaderBranch getSnapshots() {
return snapshots;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang;
/**
* Note: LiteLoader must be installed after Forge.
*
* @author huangyuhui
*/
public final class LiteLoaderInstallTask extends TaskResult<Version> {
private final DefaultDependencyManager dependencyManager;
private final String gameVersion;
private final Version version;
private final String remoteVersion;
private final LiteLoaderVersionList liteLoaderVersionList;
private RemoteVersion<LiteLoaderRemoteVersionTag> remote;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private void doRemote() {
remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion)
.orElseThrow(() -> new IllegalArgumentException("Remote LiteLoader version " + gameVersion + ", " + remoteVersion + " not found"));
}
public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
this.dependencyManager = dependencyManager;
this.gameVersion = gameVersion;
this.version = version;
this.remoteVersion = remoteVersion;
liteLoaderVersionList = (LiteLoaderVersionList) dependencyManager.getVersionList("liteloader");
if (!liteLoaderVersionList.isLoaded())
dependents.add(liteLoaderVersionList.refreshAsync(dependencyManager.getDownloadProvider())
.then(s -> {
doRemote();
return null;
}));
else
doRemote();
}
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public String getId() {
return "version";
}
@Override
public void execute() throws Exception {
Library library = new Library(
"com.mumfrey", "liteloader", remote.getSelfVersion(), null,
"http://dl.liteloader.com/versions/",
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
);
Version tempVersion = version.setLibraries(Lang.merge(remote.getTag().getLibraries(), Arrays.asList(library)));
setResult(version
.setMainClass("net.minecraft.launchwrapper.Launch")
.setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries()))
.setLogging(Collections.EMPTY_MAP)
.setMinecraftArguments(version.getMinecraftArguments().orElse("") + " --tweakClass " + remote.getTag().getTweakClass())
//.setArguments(Arguments.addGameArguments(Lang.get(version.getArguments()), "--tweakClass", remote.getTag().getTweakClass()))
);
dependencies.add(new GameLibrariesTask(dependencyManager, tempVersion));
}
}

View File

@@ -0,0 +1,49 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import java.util.Collection;
import java.util.Collections;
import org.jackhuang.hmcl.game.Library;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderRemoteVersionTag {
private final String tweakClass;
private final Collection<Library> libraries;
public LiteLoaderRemoteVersionTag() {
this("", Collections.EMPTY_SET);
}
public LiteLoaderRemoteVersionTag(String tweakClass, Collection<Library> libraries) {
this.tweakClass = tweakClass;
this.libraries = libraries;
}
public Collection<Library> getLibraries() {
return libraries;
}
public String getTweakClass() {
return tweakClass;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderRepository {
@SerializedName("stream")
private final String stream;
@SerializedName("type")
private final String type;
@SerializedName("url")
private final String url;
@SerializedName("classifier")
private final String classifier;
public LiteLoaderRepository() {
this("", "", "", "");
}
public LiteLoaderRepository(String stream, String type, String url, String classifier) {
this.stream = stream;
this.type = type;
this.url = url;
this.classifier = classifier;
}
public String getStream() {
return stream;
}
public String getType() {
return type;
}
public String getUrl() {
return url;
}
public String getClassifier() {
return classifier;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import java.util.Collection;
import java.util.Collections;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderVersion {
private final String tweakClass;
private final String file;
private final String version;
private final String md5;
private final String timestamp;
private final int lastSuccessfulBuild;
private final Collection<Library> libraries;
public LiteLoaderVersion() {
this("", "", "", "", "", 0, Collections.EMPTY_SET);
}
public LiteLoaderVersion(String tweakClass, String file, String version, String md5, String timestamp, int lastSuccessfulBuild, Collection<Library> libraries) {
this.tweakClass = tweakClass;
this.file = file;
this.version = version;
this.md5 = md5;
this.timestamp = timestamp;
this.lastSuccessfulBuild = lastSuccessfulBuild;
this.libraries = libraries;
}
public String getTweakClass() {
return tweakClass;
}
public String getFile() {
return file;
}
public String getVersion() {
return version;
}
public String getMd5() {
return md5;
}
public String getTimestamp() {
return timestamp;
}
public int getLastSuccessfulBuild() {
return lastSuccessfulBuild;
}
public Collection<Library> getLibraries() {
return Collections.unmodifiableCollection(libraries);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
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.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
public static final LiteLoaderVersionList INSTANCE = new LiteLoaderVersionList();
private LiteLoaderVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
LiteLoaderVersionsRoot root = Constants.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.parseVersion(gameVersion);
if (gg == null)
continue;
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
versions.put(key, new RemoteVersion<>(gameVersion,
v.getVersion().replace("SNAPSHOT", "SNAPSHOT-" + v.getLastSuccessfulBuild()),
snapshot
? "http://jenkins.liteloader.com/LiteLoader " + gameVersion + "/lastSuccessfulBuild/artifact/build/libs/liteloader-" + v.getVersion() + "-release.jar"
: downloadProvider.injectURL(repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile()),
new LiteLoaderRemoteVersionTag(v.getTweakClass(), v.getLibraries())
));
}
}
};
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
}

View File

@@ -0,0 +1,61 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderVersionsMeta {
@SerializedName("description")
private final String description;
@SerializedName("authors")
private final String authors;
@SerializedName("url")
private final String url;
public LiteLoaderVersionsMeta() {
this("", "", "");
}
public LiteLoaderVersionsMeta(String description, String authors, String url) {
this.description = description;
this.authors = authors;
this.url = url;
}
public String getDescription() {
return description;
}
public String getAuthors() {
return authors;
}
public String getUrl() {
return url;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import com.google.gson.annotations.SerializedName;
import java.util.Collections;
import java.util.Map;
import org.jackhuang.hmcl.util.Immutable;
/**
*
* @author huangyuhui
*/
@Immutable
public final class LiteLoaderVersionsRoot {
@SerializedName("versions")
private final Map<String, LiteLoaderGameVersions> versions;
@SerializedName("meta")
private final LiteLoaderVersionsMeta meta;
public LiteLoaderVersionsRoot() {
this(Collections.EMPTY_MAP, null);
}
public LiteLoaderVersionsRoot(Map<String, LiteLoaderGameVersions> versions, LiteLoaderVersionsMeta meta) {
this.versions = versions;
this.meta = meta;
}
public Map<String, LiteLoaderGameVersions> getVersions() {
return Collections.unmodifiableMap(versions);
}
public LiteLoaderVersionsMeta getMeta() {
return meta;
}
}

View File

@@ -0,0 +1,79 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import com.google.gson.reflect.TypeToken;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.VersionNumber;
/**
*
* @author huangyuhui
*/
public final class OptiFineBMCLVersionList extends VersionList<Void> {
public static final OptiFineBMCLVersionList INSTANCE = new OptiFineBMCLVersionList();
private OptiFineBMCLVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi.bangbang93.com/optifine/versionlist"));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
versions.clear();
Set<String> duplicates = new HashSet<>();
List<OptiFineVersion> root = Constants.GSON.fromJson(task.getResult(), new TypeToken<List<OptiFineVersion>>() {
}.getType());
for (OptiFineVersion element : root) {
String version = element.getType();
if (version == null)
continue;
String mirror = "http://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
continue;
if (StringUtils.isBlank(element.getGameVersion()))
continue;
String gameVersion = VersionNumber.parseVersion(element.getGameVersion());
versions.put(gameVersion, new RemoteVersion<>(gameVersion, version, mirror, null));
}
}
};
}
}

View File

@@ -0,0 +1,56 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.NetworkUtils;
/**
*
* @author huangyuhui
*/
public final class OptiFineDownloadFormatter extends TaskResult<String> {
private final String url;
public OptiFineDownloadFormatter(String url) {
this.url = url;
}
@Override
public void execute() throws Exception {
String result = null;
String content = NetworkUtils.doGet(NetworkUtils.toURL(url));
Matcher m = PATTERN.matcher(content);
while (m.find())
result = m.group(1);
if (result == null)
throw new IllegalStateException("Cannot find version in " + content);
setResult("http://optifine.net/downloadx?f=OptiFine" + result);
}
@Override
public String getId() {
return ID;
}
public static final String ID = "optifine_formatter";
private static final Pattern PATTERN = Pattern.compile("\"downloadx\\?f=OptiFine(.*)\"");
}

View File

@@ -0,0 +1,142 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.game.Argument;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.StringArgument;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang;
/**
* <b>Note</b>: OptiFine should be installed in the end.
*
* @author huangyuhui
*/
public final class OptiFineInstallTask extends TaskResult<Version> {
private final DefaultDependencyManager dependencyManager;
private final String gameVersion;
private final Version version;
private final String remoteVersion;
private final VersionList<?> optiFineVersionList;
private RemoteVersion<?> remote;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private void doRemote() {
remote = optiFineVersionList.getVersion(gameVersion, remoteVersion)
.orElseThrow(() -> new IllegalArgumentException("Remote OptiFine version " + gameVersion + ", " + remoteVersion + " not found"));
}
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) {
this.dependencyManager = dependencyManager;
this.gameVersion = gameVersion;
this.version = version;
this.remoteVersion = remoteVersion;
optiFineVersionList = dependencyManager.getVersionList("optifine");
if (!optiFineVersionList.isLoaded())
dependents.add(optiFineVersionList.refreshAsync(dependencyManager.getDownloadProvider())
.then(s -> {
doRemote();
return null;
}));
else
doRemote();
}
@Override
public Collection<Task> getDependents() {
return dependents;
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public String getId() {
return "version";
}
@Override
public boolean isRelyingOnDependencies() {
return false;
}
@Override
public void execute() throws Exception {
Library library = new Library(
"net.optifine", "optifine", remoteVersion, null, null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"net/optifine/optifine/" + remoteVersion + "/optifine-" + remoteVersion + ".jar",
remote.getUrl())), true
);
List<Library> libraries = new LinkedList<>();
libraries.add(library);
boolean hasFMLTweaker = false;
if (version.getMinecraftArguments().isPresent() && version.getMinecraftArguments().get().contains("FMLTweaker"))
hasFMLTweaker = true;
if (version.getArguments().isPresent()) {
List<Argument> game = version.getArguments().get().getGame();
if (game.stream().anyMatch(arg -> arg.toString(Collections.EMPTY_MAP, Collections.EMPTY_MAP).contains("FMLTweaker")))
hasFMLTweaker = true;
}
/*Arguments arguments = Lang.get(version.getArguments());
if (!hasFMLTweaker)
arguments = Arguments.addGameArguments(arguments, "--tweakClass", "optifine.OptiFineTweaker");
*/
String minecraftArguments = version.getMinecraftArguments().orElse("");
if (!hasFMLTweaker)
minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineTweaker";
if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper."))
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
setResult(version
.setLibraries(Lang.merge(version.getLibraries(), libraries))
.setMainClass("net.minecraft.launchwrapper.Launch")
.setMinecraftArguments(minecraftArguments)
//.setArguments(arguments)
);
dependencies.add(new GameLibrariesTask(dependencyManager, version.setLibraries(libraries)));
}
}

View File

@@ -0,0 +1,90 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import com.google.gson.annotations.SerializedName;
/**
*
* @author huangyuhui
*/
public final class OptiFineVersion {
@SerializedName("dl")
private final String downloadLink;
@SerializedName("ver")
private final String version;
@SerializedName("date")
private final String date;
@SerializedName("type")
private final String type;
@SerializedName("patch")
private final String patch;
@SerializedName("mirror")
private final String mirror;
@SerializedName("mcversion")
private final String gameVersion;
public OptiFineVersion() {
this(null, null, null, null, null, null, null);
}
public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) {
this.downloadLink = downloadLink;
this.version = version;
this.date = date;
this.type = type;
this.patch = patch;
this.mirror = mirror;
this.gameVersion = gameVersion;
}
public String getDownloadLink() {
return downloadLink;
}
public String getVersion() {
return version;
}
public String getDate() {
return date;
}
public String getType() {
return type;
}
public String getPatch() {
return patch;
}
public String getMirror() {
return mirror;
}
public String getGameVersion() {
return gameVersion;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.optifine;
import java.io.ByteArrayInputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
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.NetworkUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
*
* @author huangyuhui
*/
public final class OptiFineVersionList extends VersionList<Void> {
private static final Pattern PATTERN = Pattern.compile("OptiFine (.*?) ");
public static final OptiFineVersionList INSTANCE = new OptiFineVersionList();
private OptiFineVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://optifine.net/downloads"));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
versions.clear();
String html = task.getResult().replace("&nbsp;", " ").replace("&gt;", ">").replace("&lt;", "<").replace("<br>", "<br />");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = factory.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8")));
Element r = doc.getDocumentElement();
NodeList tables = r.getElementsByTagName("table");
for (int i = 0; i < tables.getLength(); i++) {
Element e = (Element) tables.item(i);
if ("downloadTable".equals(e.getAttribute("class"))) {
NodeList tr = e.getElementsByTagName("tr");
for (int k = 0; k < tr.getLength(); k++) {
NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
String url = null, version = null, gameVersion = null;
for (int j = 0; j < downloadLine.getLength(); j++) {
Element td = (Element) downloadLine.item(j);
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror"))
url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile"))
version = td.getTextContent();
}
Matcher matcher = PATTERN.matcher(version);
while (matcher.find())
gameVersion = matcher.group(1);
if (gameVersion == null || version == null || url == null)
continue;
versions.put(gameVersion, new RemoteVersion<>(gameVersion, version, url, null));
}
}
}
}
};
}
}

View File

@@ -0,0 +1,85 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.event;
import java.util.EventObject;
/**
*
* @author huangyuhui
*/
public class Event extends EventObject {
public Event(Object source) {
super(source);
}
private boolean canceled;
/**
* true if this event is canceled.
*
* @throws UnsupportedOperationException if trying to cancel a non-cancelable event.
*/
public final boolean isCanceled() {
return canceled;
}
/**
*
* @param canceled new value
* @throws UnsupportedOperationException if trying to cancel a non-cancelable event.
*/
public final void setCanceled(boolean canceled) {
if (!isCancelable())
throw new UnsupportedOperationException("Attempted to cancel a non-cancelable event: " + getClass());
this.canceled = canceled;
}
/**
* true if this Event this cancelable.
*/
public boolean isCancelable() {
return false;
}
public boolean hasResult() {
return false;
}
private Result result;
/**
* Retutns the value set as the result of this event
*/
public Result getResult() {
return result;
}
public void setResult(Result result) {
if (!hasResult())
throw new UnsupportedOperationException("Attempted to set result on a no result event: " + this.getClass() + " of type.");
this.result = result;
}
public enum Result {
DENY,
DEFAULT,
ALLOW
}
}

View File

@@ -0,0 +1,43 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.event;
import java.util.EventObject;
import java.util.HashMap;
import org.jackhuang.hmcl.task.Schedulers;
/**
*
* @author huangyuhui
*/
public final class EventBus {
private final HashMap<Class<?>, EventManager<?>> events = new HashMap<>();
public <T extends EventObject> EventManager<T> channel(Class<T> clazz) {
if (!events.containsKey(clazz))
events.put(clazz, new EventManager<>(Schedulers.computation()));
return (EventManager<T>) events.get(clazz);
}
public void fireEvent(EventObject obj) {
channel((Class<EventObject>) obj.getClass()).fireEvent(obj);
}
public static final EventBus EVENT_BUS = new EventBus();
}

View File

@@ -0,0 +1,85 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.event;
import java.util.EnumMap;
import java.util.EventObject;
import java.util.HashSet;
import java.util.function.Consumer;
import org.jackhuang.hmcl.task.Scheduler;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.SimpleMultimap;
/**
*
* @author huangyuhui
*/
public final class EventManager<T extends EventObject> {
private final Scheduler scheduler;
private final SimpleMultimap<EventPriority, Consumer<T>> handlers
= new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), HashSet::new);
private final SimpleMultimap<EventPriority, Runnable> handlers2
= new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), HashSet::new);
public EventManager() {
this(Schedulers.immediate());
}
public EventManager(Scheduler scheduler) {
this.scheduler = scheduler;
}
public void register(Consumer<T> consumer) {
register(consumer, EventPriority.NORMAL);
}
public void register(Consumer<T> consumer, EventPriority priority) {
if (!handlers.get(priority).contains(consumer))
handlers.put(priority, consumer);
}
public void register(Runnable runnable) {
register(runnable, EventPriority.NORMAL);
}
public void register(Runnable runnable, EventPriority priority) {
if (!handlers2.get(priority).contains(runnable))
handlers2.put(priority, runnable);
}
public void unregister(Consumer<T> consumer) {
handlers.removeValue(consumer);
}
public void unregister(Runnable runnable) {
handlers2.removeValue(runnable);
}
public void fireEvent(T event) {
scheduler.schedule(() -> {
for (EventPriority priority : EventPriority.values()) {
for (Consumer<T> handler : handlers.get(priority))
handler.accept(event);
for (Runnable runnable : handlers2.get(priority))
runnable.run();
}
});
}
}

View File

@@ -15,12 +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.event
package org.jackhuang.hmcl.event;
enum class EventPriority {
/**
*
* @author huangyuhui
*/
public enum EventPriority {
HIGHEST,
HIGH,
NORMAL,
LOW,
LOWEST
}
}

View File

@@ -0,0 +1,49 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.event;
import java.util.EventObject;
/**
*
* @author huang
*/
public class FailedEvent<T> extends EventObject {
private final int failedTime;
private T newResult;
public FailedEvent(Object source, int failedTime, T newResult) {
super(source);
this.failedTime = failedTime;
this.newResult = newResult;
}
public int getFailedTime() {
return failedTime;
}
public T getNewResult() {
return newResult;
}
public void setNewResult(T newResult) {
this.newResult = newResult;
}
}

Some files were not shown because too many files have changed in this diff Show More