mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-17 07:47:57 -04:00
UI works
This commit is contained in:
parent
3c32ef39c7
commit
41498b5d93
43
HMCL/src/main/java/org/jackhuang/hmcl/Events.kt
Normal file
43
HMCL/src/main/java/org/jackhuang/hmcl/Events.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.setting.Profile
|
||||||
|
import java.util.EventObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when the selected profile changed.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.setting.Settings]
|
||||||
|
* *
|
||||||
|
* @param Profile the new profile.
|
||||||
|
* *
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class ProfileChangedEvent(source: Any, val value: Profile) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when loading profiles.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.setting.Settings]
|
||||||
|
* *
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class ProfileLoadingEvent(source: Any) : EventObject(source)
|
@ -17,7 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.game
|
package org.jackhuang.hmcl.game
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import javafx.beans.InvalidationListener
|
||||||
|
import org.jackhuang.hmcl.setting.VersionSetting
|
||||||
|
import org.jackhuang.hmcl.util.GSON
|
||||||
import org.jackhuang.hmcl.util.LOG
|
import org.jackhuang.hmcl.util.LOG
|
||||||
|
import org.jackhuang.hmcl.util.fromJson
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
@ -25,19 +30,69 @@ import java.util.logging.Level
|
|||||||
class HMCLGameRepository(baseDirectory: File)
|
class HMCLGameRepository(baseDirectory: File)
|
||||||
: DefaultGameRepository(baseDirectory) {
|
: DefaultGameRepository(baseDirectory) {
|
||||||
|
|
||||||
val PROFILE = "{\"selectedProfile\": \"(Default)\",\"profiles\": {\"(Default)\": {\"name\": \"(Default)\"}},\"clientToken\": \"88888888-8888-8888-8888-888888888888\"}"
|
private val versionSettings = HashMap<String, VersionSetting>()
|
||||||
|
|
||||||
@Synchronized
|
override fun refreshVersionsImpl() {
|
||||||
override fun refreshVersions() {
|
versionSettings.clear()
|
||||||
super.refreshVersions()
|
|
||||||
|
super.refreshVersionsImpl()
|
||||||
|
|
||||||
|
versions.keys.forEach(this::loadVersionSetting)
|
||||||
|
|
||||||
|
checkModpack()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val file = baseDirectory.resolve("launcher_profiles.json")
|
val file = baseDirectory.resolve("launcher_profiles.json")
|
||||||
if (!file.exists())
|
if (!file.exists() && versions.isNotEmpty())
|
||||||
file.writeText(PROFILE)
|
file.writeText(PROFILE)
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
LOG.log(Level.WARNING, "Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex)
|
LOG.log(Level.WARNING, "Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkModpack() {}
|
||||||
|
|
||||||
|
private fun getVersionSettingFile(id: String) = getVersionRoot(id).resolve("hmclversion.cfg")
|
||||||
|
|
||||||
|
private fun loadVersionSetting(id: String) {
|
||||||
|
val file = getVersionSettingFile(id)
|
||||||
|
if (file.exists()) {
|
||||||
|
try {
|
||||||
|
val versionSetting = GSON.fromJson<VersionSetting>(file.readText())!!
|
||||||
|
initVersionSetting(id, versionSetting)
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveVersionSetting(id: String) {
|
||||||
|
if (!versionSettings.containsKey(id))
|
||||||
|
return
|
||||||
|
|
||||||
|
getVersionSettingFile(id).writeText(GSON.toJson(versionSettings[id]))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initVersionSetting(id: String, vs: VersionSetting): VersionSetting {
|
||||||
|
vs.addPropertyChangedListener(InvalidationListener { saveVersionSetting(id) })
|
||||||
|
versionSettings[id] = vs
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun createVersionSetting(id: String): VersionSetting? {
|
||||||
|
if (!hasVersion(id)) return null
|
||||||
|
return versionSettings[id] ?: initVersionSetting(id, VersionSetting())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVersionSetting(id: String): VersionSetting? {
|
||||||
|
if (!versionSettings.containsKey(id))
|
||||||
|
loadVersionSetting(id)
|
||||||
|
return versionSettings[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val PROFILE = "{\"selectedProfile\": \"(Default)\",\"profiles\": {\"(Default)\": {\"name\": \"(Default)\"}},\"clientToken\": \"88888888-8888-8888-8888-888888888888\"}"
|
||||||
|
val GSON = GsonBuilder().registerTypeAdapter(VersionSetting::class.java, VersionSetting).setPrettyPrinting().create()
|
||||||
|
}
|
||||||
}
|
}
|
123
HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt
Normal file
123
HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.setting
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import org.jackhuang.hmcl.MainApplication
|
||||||
|
import org.jackhuang.hmcl.util.JavaVersion
|
||||||
|
import java.io.File
|
||||||
|
import java.util.TreeMap
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
@SerializedName("last")
|
||||||
|
var last: String = ""
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("bgpath")
|
||||||
|
var bgpath: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("commonpath")
|
||||||
|
var commonpath: File = MainApplication.getMinecraftDirectory()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("proxyHost")
|
||||||
|
var proxyHost: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("proxyPort")
|
||||||
|
var proxyPort: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("proxyUserName")
|
||||||
|
var proxyUserName: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("proxyPassword")
|
||||||
|
var proxyPassword: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("theme")
|
||||||
|
var theme: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("java")
|
||||||
|
var java: List<JavaVersion>? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("localization")
|
||||||
|
var localization: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("downloadtype")
|
||||||
|
var downloadtype: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("configurations")
|
||||||
|
var configurations: MutableMap<String, Profile> = TreeMap()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("accounts")
|
||||||
|
var accounts: MutableMap<String, MutableMap<*, *>> = TreeMap()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("fontFamily")
|
||||||
|
var fontFamily: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("fontSize")
|
||||||
|
var fontSize: Int = 12
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
@SerializedName("logLines")
|
||||||
|
var logLines: Int = 100
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
}
|
117
HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt
Normal file
117
HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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.setting
|
||||||
|
|
||||||
|
import com.google.gson.*
|
||||||
|
import javafx.beans.InvalidationListener
|
||||||
|
import javafx.beans.property.*
|
||||||
|
import org.jackhuang.hmcl.game.HMCLGameRepository
|
||||||
|
import org.jackhuang.hmcl.util.*
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
class Profile(var name: String = "Default", gameDir: File = File(".minecraft")) {
|
||||||
|
val globalProperty = SimpleObjectProperty<VersionSetting>(this, "global", VersionSetting())
|
||||||
|
var global: VersionSetting by globalProperty
|
||||||
|
|
||||||
|
val selectedVersionProperty = SimpleStringProperty(this, "selectedVersion", "")
|
||||||
|
var selectedVersion: String by selectedVersionProperty
|
||||||
|
|
||||||
|
val gameDirProperty = SimpleObjectProperty<File>(this, "gameDir", gameDir)
|
||||||
|
var gameDir: File by gameDirProperty
|
||||||
|
|
||||||
|
val noCommonProperty = SimpleBooleanProperty(this, "noCommon", false)
|
||||||
|
var noCommon: Boolean by noCommonProperty
|
||||||
|
|
||||||
|
var repository = HMCLGameRepository(gameDir)
|
||||||
|
|
||||||
|
init {
|
||||||
|
gameDirProperty.addListener { _, _, newValue ->
|
||||||
|
repository.baseDirectory = newValue
|
||||||
|
repository.refreshVersions()
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedVersionProperty.addListener { _, _, newValue ->
|
||||||
|
if (newValue.isNotBlank() && !repository.hasVersion(newValue)) {
|
||||||
|
val newVersion = repository.getVersions().firstOrNull()
|
||||||
|
// will cause anthor change event, we must insure that there will not be dead recursion.
|
||||||
|
selectedVersion = newVersion?.id ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun specializeVersionSetting(id: String) {
|
||||||
|
var vs = repository.getVersionSetting(id)
|
||||||
|
if (vs == null)
|
||||||
|
vs = repository.createVersionSetting(id) ?: return
|
||||||
|
vs.usesGlobal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun globalizeVersionSetting(id: String) {
|
||||||
|
repository.getVersionSetting(id)?.usesGlobal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVersionGlobal(id: String): Boolean {
|
||||||
|
return repository.getVersionSetting(id)?.usesGlobal ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVersionSetting(id: String): VersionSetting {
|
||||||
|
val vs = repository.getVersionSetting(id)
|
||||||
|
if (vs == null || vs.usesGlobal) {
|
||||||
|
global.isGlobal = true // always keep global.isGlobal = true
|
||||||
|
return global
|
||||||
|
} else
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedVersionSetting(): VersionSetting =
|
||||||
|
getVersionSetting(selectedVersion)
|
||||||
|
|
||||||
|
fun addPropertyChangedListener(listener: InvalidationListener) {
|
||||||
|
globalProperty.addListener(listener)
|
||||||
|
selectedVersionProperty.addListener(listener)
|
||||||
|
gameDirProperty.addListener(listener)
|
||||||
|
noCommonProperty.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Serializer: JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||||
|
override fun serialize(src: Profile?, typeOfSrc: Type?, context: JsonSerializationContext): JsonElement {
|
||||||
|
if (src == null) return JsonNull.INSTANCE
|
||||||
|
val jsonObject = JsonObject()
|
||||||
|
with(jsonObject) {
|
||||||
|
add("global", context.serialize(src.global))
|
||||||
|
addProperty("selectedVersion", src.selectedVersion)
|
||||||
|
addProperty("gameDir", src.gameDir.path)
|
||||||
|
addProperty("noCommon", src.noCommon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Profile? {
|
||||||
|
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
|
||||||
|
|
||||||
|
return Profile(gameDir = File(json["gameDir"]?.asString ?: "")).apply {
|
||||||
|
global = context.deserialize(json["global"], VersionSetting::class.java)
|
||||||
|
selectedVersion = json["selectedVersion"]?.asString ?: ""
|
||||||
|
noCommon = json["noCommon"]?.asBoolean ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,154 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.setting
|
package org.jackhuang.hmcl.setting
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import javafx.beans.InvalidationListener
|
||||||
|
import java.io.IOException
|
||||||
|
import org.jackhuang.hmcl.MainApplication
|
||||||
|
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||||
|
import org.jackhuang.hmcl.download.DownloadProvider
|
||||||
|
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
||||||
|
import org.jackhuang.hmcl.util.GSON
|
||||||
|
import org.jackhuang.hmcl.util.LOG
|
||||||
|
import java.io.File
|
||||||
|
import java.util.logging.Level
|
||||||
|
import org.jackhuang.hmcl.ProfileLoadingEvent
|
||||||
|
import org.jackhuang.hmcl.ProfileChangedEvent
|
||||||
|
import org.jackhuang.hmcl.event.EVENT_BUS
|
||||||
|
import org.jackhuang.hmcl.util.FileTypeAdapter
|
||||||
|
|
||||||
|
|
||||||
object Settings {
|
object Settings {
|
||||||
|
val GSON = GsonBuilder()
|
||||||
|
.registerTypeAdapter(VersionSetting::class.java, VersionSetting)
|
||||||
|
.registerTypeAdapter(Profile::class.java, Profile)
|
||||||
|
.registerTypeAdapter(File::class.java, FileTypeAdapter)
|
||||||
|
.setPrettyPrinting().create()
|
||||||
|
|
||||||
|
val DEFAULT_PROFILE = "Default"
|
||||||
|
val HOME_PROFILE = "Home"
|
||||||
|
|
||||||
|
val SETTINGS_FILE = File("hmcl.json").absoluteFile
|
||||||
|
|
||||||
|
val SETTINGS: Config
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
SETTINGS = initSettings();
|
||||||
|
save()
|
||||||
|
|
||||||
|
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
||||||
|
getProfiles().put(DEFAULT_PROFILE, Profile());
|
||||||
|
|
||||||
|
for ((name, profile) in getProfiles().entries) {
|
||||||
|
profile.name = name
|
||||||
|
profile.addPropertyChangedListener(InvalidationListener { save() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) {
|
||||||
|
0 -> MojangDownloadProvider
|
||||||
|
1 -> BMCLAPIDownloadProvider
|
||||||
|
else -> MojangDownloadProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initSettings(): Config {
|
||||||
|
var c = Config()
|
||||||
|
if (SETTINGS_FILE.exists())
|
||||||
|
try {
|
||||||
|
val str = SETTINGS_FILE.readText()
|
||||||
|
if (str.trim() == "")
|
||||||
|
LOG.finer("Settings file is empty, use the default settings.")
|
||||||
|
else {
|
||||||
|
val d = GSON.fromJson(str, Config::class.java)
|
||||||
|
if (d != null)
|
||||||
|
c = d
|
||||||
|
}
|
||||||
|
LOG.finest("Initialized settings.")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG.config("No settings file here, may be first loading.")
|
||||||
|
if (!c.configurations.containsKey(HOME_PROFILE))
|
||||||
|
c.configurations[HOME_PROFILE] = Profile(HOME_PROFILE, MainApplication.getMinecraftDirectory())
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
try {
|
||||||
|
SETTINGS_FILE.writeText(GSON.toJson(SETTINGS))
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
LOG.log(Level.SEVERE, "Failed to save config", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastProfile(): Profile {
|
||||||
|
if (!hasProfile(SETTINGS.last))
|
||||||
|
SETTINGS.last = DEFAULT_PROFILE
|
||||||
|
return getProfile(SETTINGS.last)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProfile(name: String?): Profile {
|
||||||
|
var p: Profile? = getProfiles()[name ?: DEFAULT_PROFILE]
|
||||||
|
if (p == null)
|
||||||
|
if (getProfiles().containsKey(DEFAULT_PROFILE))
|
||||||
|
p = getProfiles()[DEFAULT_PROFILE]!!
|
||||||
|
else {
|
||||||
|
p = Profile()
|
||||||
|
getProfiles().put(DEFAULT_PROFILE, p)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasProfile(name: String?): Boolean {
|
||||||
|
return getProfiles().containsKey(name ?: DEFAULT_PROFILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProfiles(): MutableMap<String, Profile> {
|
||||||
|
return SETTINGS.configurations
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProfilesFiltered(): Collection<Profile> {
|
||||||
|
return getProfiles().values.filter { t -> t.name.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putProfile(ver: Profile?): Boolean {
|
||||||
|
if (ver == null || ver.name.isBlank() || getProfiles().containsKey(ver.name))
|
||||||
|
return false
|
||||||
|
getProfiles().put(ver.name, ver)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delProfile(ver: Profile): Boolean {
|
||||||
|
return delProfile(ver.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delProfile(ver: String): Boolean {
|
||||||
|
if (DEFAULT_PROFILE == ver) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var notify = false
|
||||||
|
if (getLastProfile().name == ver)
|
||||||
|
notify = true
|
||||||
|
val flag = getProfiles().remove(ver) != null
|
||||||
|
if (notify && flag)
|
||||||
|
onProfileChanged()
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun onProfileChanged() {
|
||||||
|
val p = getLastProfile()
|
||||||
|
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, p))
|
||||||
|
p.repository.refreshVersions()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start profiles loading process.
|
||||||
|
* Invoked by loading GUI phase.
|
||||||
|
*/
|
||||||
|
fun onProfileLoading() {
|
||||||
|
EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS))
|
||||||
|
onProfileChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,17 +18,14 @@
|
|||||||
package org.jackhuang.hmcl.setting
|
package org.jackhuang.hmcl.setting
|
||||||
|
|
||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
|
import javafx.beans.InvalidationListener
|
||||||
import javafx.beans.property.*
|
import javafx.beans.property.*
|
||||||
import org.jackhuang.hmcl.util.*
|
import org.jackhuang.hmcl.util.*
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
class VersionSetting() {
|
class VersionSetting() {
|
||||||
|
|
||||||
/**
|
var isGlobal: Boolean = false
|
||||||
* The displayed name.
|
|
||||||
*/
|
|
||||||
val nameProperty = SimpleStringProperty(this, "name", "")
|
|
||||||
var name: String by nameProperty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMCL Version Settings have been divided into 2 parts.
|
* HMCL Version Settings have been divided into 2 parts.
|
||||||
@ -162,15 +159,31 @@ class VersionSetting() {
|
|||||||
val launcherVisibilityProperty = SimpleObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE)
|
val launcherVisibilityProperty = SimpleObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE)
|
||||||
var launcherVisibility: LauncherVisibility by launcherVisibilityProperty
|
var launcherVisibility: LauncherVisibility by launcherVisibilityProperty
|
||||||
|
|
||||||
val gameVersion: String
|
fun addPropertyChangedListener(listener: InvalidationListener) {
|
||||||
get() = "1.7.10"
|
usesGlobalProperty.addListener(listener)
|
||||||
|
javaProperty.addListener(listener)
|
||||||
|
javaDirProperty.addListener(listener)
|
||||||
|
wrapperProperty.addListener(listener)
|
||||||
|
permSizeProperty.addListener(listener)
|
||||||
|
maxMemoryProperty.addListener(listener)
|
||||||
|
precalledCommandProperty.addListener(listener)
|
||||||
|
javaArgsProperty.addListener(listener)
|
||||||
|
minecraftArgsProperty.addListener(listener)
|
||||||
|
noJVMArgsProperty.addListener(listener)
|
||||||
|
notCheckGameProperty.addListener(listener)
|
||||||
|
serverIpProperty.addListener(listener)
|
||||||
|
fullscreenProperty.addListener(listener)
|
||||||
|
widthProperty.addListener(listener)
|
||||||
|
heightProperty.addListener(listener)
|
||||||
|
gameDirTypeProperty.addListener(listener)
|
||||||
|
launcherVisibilityProperty.addListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
companion object Serializer: JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
companion object Serializer: JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||||
override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||||
if (src == null) return JsonNull.INSTANCE
|
if (src == null) return JsonNull.INSTANCE
|
||||||
val jsonObject = JsonObject()
|
val jsonObject = JsonObject()
|
||||||
with(jsonObject) {
|
with(jsonObject) {
|
||||||
addProperty("name", src.name)
|
|
||||||
addProperty("usesGlobal", src.usesGlobal)
|
addProperty("usesGlobal", src.usesGlobal)
|
||||||
addProperty("javaArgs", src.javaArgs)
|
addProperty("javaArgs", src.javaArgs)
|
||||||
addProperty("minecraftArgs", src.minecraftArgs)
|
addProperty("minecraftArgs", src.minecraftArgs)
|
||||||
@ -197,7 +210,6 @@ class VersionSetting() {
|
|||||||
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
|
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
|
||||||
|
|
||||||
return VersionSetting().apply {
|
return VersionSetting().apply {
|
||||||
name = json["name"]?.asString ?: ""
|
|
||||||
usesGlobal = json["usesGlobal"]?.asBoolean ?: false
|
usesGlobal = json["usesGlobal"]?.asBoolean ?: false
|
||||||
javaArgs = json["javaArgs"]?.asString ?: ""
|
javaArgs = json["javaArgs"]?.asString ?: ""
|
||||||
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
||||||
|
@ -21,10 +21,17 @@ import com.jfoenix.controls.JFXButton
|
|||||||
import com.jfoenix.controls.JFXComboBox
|
import com.jfoenix.controls.JFXComboBox
|
||||||
import com.jfoenix.controls.JFXListCell
|
import com.jfoenix.controls.JFXListCell
|
||||||
import com.jfoenix.controls.JFXListView
|
import com.jfoenix.controls.JFXListView
|
||||||
|
import javafx.collections.FXCollections
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
import javafx.scene.layout.StackPane
|
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.RefreshedVersionsEvent
|
||||||
|
import org.jackhuang.hmcl.game.minecraftVersion
|
||||||
|
import org.jackhuang.hmcl.setting.Settings
|
||||||
import org.jackhuang.hmcl.setting.VersionSetting
|
import org.jackhuang.hmcl.setting.VersionSetting
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
|
||||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
|
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
|
||||||
@ -51,7 +58,7 @@ class MainController {
|
|||||||
*/
|
*/
|
||||||
@FXML lateinit var page: StackPane
|
@FXML lateinit var page: StackPane
|
||||||
|
|
||||||
@FXML lateinit var listVersions: JFXListView<VersionSetting> // TODO: JFXListView<Version> including icon, title, game version(if equals to title, hidden)
|
@FXML lateinit var listVersions: JFXListView<VersionListItem> // TODO: JFXListView<Version> including icon, title, game version(if equals to title, hidden)
|
||||||
|
|
||||||
lateinit var animationHandler: TransitionHandler
|
lateinit var animationHandler: TransitionHandler
|
||||||
|
|
||||||
@ -61,56 +68,23 @@ class MainController {
|
|||||||
|
|
||||||
animationHandler = TransitionHandler(page)
|
animationHandler = TransitionHandler(page)
|
||||||
|
|
||||||
listVersions.items.add(VersionSetting("1"))
|
EVENT_BUS.channel<RefreshedVersionsEvent>() += this::loadVersions
|
||||||
listVersions.items.add(VersionSetting("2"))
|
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
|
||||||
listVersions.items.add(VersionSetting("3"))
|
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
|
||||||
listVersions.items.add(VersionSetting("4"))
|
|
||||||
listVersions.items.add(VersionSetting("5"))
|
|
||||||
listVersions.items.add(VersionSetting("6"))
|
|
||||||
listVersions.items.add(VersionSetting("7"))
|
|
||||||
listVersions.items.add(VersionSetting("8"))
|
|
||||||
listVersions.items.add(VersionSetting("9"))
|
|
||||||
listVersions.items.add(VersionSetting("10"))
|
|
||||||
listVersions.items.add(VersionSetting("11"))
|
|
||||||
listVersions.items.add(VersionSetting("12"))
|
|
||||||
|
|
||||||
listVersions.setCellFactory {
|
|
||||||
object : JFXListCell<VersionSetting>() {
|
|
||||||
override fun updateItem(item: VersionSetting?, empty: Boolean) {
|
|
||||||
super.updateItem(item, empty)
|
|
||||||
|
|
||||||
if (item == null || empty) return
|
|
||||||
val g = VersionListItem(item, item.gameVersion)
|
|
||||||
g.onSettingsButtonClicked {
|
|
||||||
setContentPage(Controllers.versionPane)
|
|
||||||
Controllers.versionController.loadVersionSetting(g.setting)
|
|
||||||
}
|
|
||||||
graphic = g
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listVersions.setOnMouseClicked {
|
listVersions.setOnMouseClicked {
|
||||||
if (it.clickCount == 2) {
|
if (it.clickCount == 2) {
|
||||||
setContentPage(Controllers.versionPane)
|
setContentPage(Controllers.versionPane)
|
||||||
Controllers.versionController.loadVersionSetting(listVersions.selectionModel.selectedItem)
|
val id = listVersions.selectionModel.selectedItem.id
|
||||||
|
|
||||||
|
Controllers.versionController.loadVersionSetting(id, Settings.getLastProfile().getVersionSetting(id))
|
||||||
} else
|
} else
|
||||||
it.consume()
|
it.consume()
|
||||||
}
|
}
|
||||||
|
|
||||||
comboProfiles.items.add("SA")
|
|
||||||
comboProfiles.items.add("SB")
|
|
||||||
comboProfiles.items.add("SC")
|
|
||||||
comboProfiles.items.add("SD")
|
|
||||||
comboProfiles.items.add("SE")
|
|
||||||
comboProfiles.items.add("SF")
|
|
||||||
comboProfiles.items.add("SG")
|
|
||||||
comboProfiles.items.add("SH")
|
|
||||||
comboProfiles.items.add("SI")
|
|
||||||
comboProfiles.items.add("SJ")
|
|
||||||
comboProfiles.items.add("SK")
|
|
||||||
|
|
||||||
listVersions.smoothScrolling()
|
listVersions.smoothScrolling()
|
||||||
|
|
||||||
|
Settings.onProfileLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val empty = Pane()
|
private val empty = Pane()
|
||||||
@ -122,4 +96,34 @@ class MainController {
|
|||||||
fun installNewVersion() {
|
fun installNewVersion() {
|
||||||
setContentPage(Wizard.createWizard("Install New Game", DownloadWizardProvider()))
|
setContentPage(Wizard.createWizard("Install New Game", DownloadWizardProvider()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onProfilesLoading() {
|
||||||
|
// TODO: Profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onProfileChanged(event: ProfileChangedEvent) {
|
||||||
|
val profile = event.value
|
||||||
|
profile.selectedVersionProperty.addListener { _, _, newValue ->
|
||||||
|
versionChanged(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val versionListItems = mutableMapOf<String, VersionListItem>()
|
||||||
|
|
||||||
|
fun loadVersions() {
|
||||||
|
val profile = Settings.getLastProfile()
|
||||||
|
val list = mutableListOf<VersionListItem>()
|
||||||
|
versionListItems.clear()
|
||||||
|
profile.repository.getVersions().forEach {
|
||||||
|
val item = VersionListItem(it.id, minecraftVersion(Settings.getLastProfile().repository.getVersionJar(it.id)) ?: "Unknown")
|
||||||
|
list += item
|
||||||
|
versionListItems += it.id to item
|
||||||
|
}
|
||||||
|
|
||||||
|
listVersions.items = FXCollections.observableList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun versionChanged(selectedVersion: String) {
|
||||||
|
listVersions.selectionModel.select(versionListItems[selectedVersion])
|
||||||
|
}
|
||||||
}
|
}
|
@ -59,8 +59,8 @@ class VersionController {
|
|||||||
JFXScrollPane.smoothScrolling(scroll)
|
JFXScrollPane.smoothScrolling(scroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadVersionSetting(version: VersionSetting) {
|
fun loadVersionSetting(id: String, version: VersionSetting) {
|
||||||
titleLabel.text = version.name
|
titleLabel.text = id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onExploreJavaDir() {
|
fun onExploreJavaDir() {
|
||||||
|
@ -22,7 +22,7 @@ import javafx.scene.control.Label
|
|||||||
import javafx.scene.layout.BorderPane
|
import javafx.scene.layout.BorderPane
|
||||||
import org.jackhuang.hmcl.setting.VersionSetting
|
import org.jackhuang.hmcl.setting.VersionSetting
|
||||||
|
|
||||||
class VersionListItem(val setting: VersionSetting, val gameVersion: String) : BorderPane() {
|
class VersionListItem(val versionName: String, val gameVersion: String) : BorderPane() {
|
||||||
|
|
||||||
@FXML lateinit var lblVersionName: Label
|
@FXML lateinit var lblVersionName: Label
|
||||||
@FXML lateinit var lblGameVersion: Label
|
@FXML lateinit var lblGameVersion: Label
|
||||||
@ -31,7 +31,7 @@ class VersionListItem(val setting: VersionSetting, val gameVersion: String) : Bo
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
loadFXML("/assets/fxml/version-list-item.fxml")
|
loadFXML("/assets/fxml/version-list-item.fxml")
|
||||||
lblVersionName.text = setting.name
|
lblVersionName.text = versionName
|
||||||
lblGameVersion.text = gameVersion
|
lblGameVersion.text = gameVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import org.jackhuang.hmcl.task.Task
|
|||||||
import org.jackhuang.hmcl.task.then
|
import org.jackhuang.hmcl.task.then
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
class DefaultDependencyManager(override val repository: DefaultGameRepository, override val downloadProvider: DownloadProvider, val proxy: Proxy = Proxy.NO_PROXY)
|
class DefaultDependencyManager(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, val proxy: Proxy = Proxy.NO_PROXY)
|
||||||
: AbstractDependencyManager(repository) {
|
: AbstractDependencyManager(repository) {
|
||||||
|
|
||||||
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)
|
override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this)
|
||||||
|
@ -35,4 +35,6 @@ class EventBus {
|
|||||||
channel(obj.javaClass).fireEvent(obj)
|
channel(obj.javaClass).fireEvent(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val EVENT_BUS = EventBus()
|
@ -17,30 +17,43 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.event
|
package org.jackhuang.hmcl.event
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.SimpleMultimap
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class EventManager<T : EventObject> {
|
class EventManager<T : EventObject> {
|
||||||
private val handlers = EnumMap<EventPriority, MutableList<(T) -> Unit>>(EventPriority::class.java).apply {
|
private val handlers = SimpleMultimap<EventPriority, (T) -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
||||||
for (value in EventPriority.values())
|
private val handlers2 = SimpleMultimap<EventPriority, () -> Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet)
|
||||||
put(value, LinkedList<(T) -> Unit>())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun register(func: (T) -> Unit, priority: EventPriority = EventPriority.NORMAL) {
|
fun register(func: (T) -> Unit, priority: EventPriority = EventPriority.NORMAL) {
|
||||||
if (!handlers[priority]!!.contains(func))
|
if (!handlers[priority].contains(func))
|
||||||
handlers[priority]!!.add(func)
|
handlers.put(priority, func)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(func: () -> Unit, priority: EventPriority = EventPriority.NORMAL) {
|
||||||
|
if (!handlers2[priority].contains(func))
|
||||||
|
handlers2.put(priority, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregister(func: (T) -> Unit) {
|
fun unregister(func: (T) -> Unit) {
|
||||||
EventPriority.values().forEach { handlers[it]!!.remove(func) }
|
handlers.remove(func)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregister(func: () -> Unit) {
|
||||||
|
handlers2.remove(func)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fireEvent(event: T) {
|
fun fireEvent(event: T) {
|
||||||
for (priority in EventPriority.values())
|
for (priority in EventPriority.values()) {
|
||||||
for (handler in handlers[priority]!!)
|
for (handler in handlers[priority])
|
||||||
handler(event)
|
handler(event)
|
||||||
|
for (handler in handlers2[priority])
|
||||||
|
handler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun plusAssign(func: (T) -> Unit) = register(func)
|
operator fun plusAssign(func: (T) -> Unit) = register(func)
|
||||||
|
operator fun plusAssign(func: () -> Unit) = register(func)
|
||||||
operator fun minusAssign(func: (T) -> Unit) = unregister(func)
|
operator fun minusAssign(func: (T) -> Unit) = unregister(func)
|
||||||
|
operator fun minusAssign(func: () -> Unit) = unregister(func)
|
||||||
operator fun invoke(event: T) = fireEvent(event)
|
operator fun invoke(event: T) = fireEvent(event)
|
||||||
}
|
}
|
51
HMCLCore/src/main/java/org/jackhuang/hmcl/event/Events.kt
Normal file
51
HMCLCore/src/main/java/org/jackhuang/hmcl/event/Events.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.event
|
||||||
|
|
||||||
|
import java.util.EventObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when loading versions in a .minecraft folder.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
|
||||||
|
* *
|
||||||
|
* @param IMinecraftService .minecraft folder.
|
||||||
|
* *
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class RefreshingVersionsEvent(source: Any) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when all the versions in .minecraft folder are loaded.
|
||||||
|
* <br>
|
||||||
|
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
|
||||||
|
* @param source [org.jackhuang.hmcl.game.GameRepository]
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class RefreshedVersionsEvent(source: Any) : EventObject(source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event gets fired when a minecraft version has been loaded.
|
||||||
|
* <br></br>
|
||||||
|
* This event is fired on the [org.jackhuang.hmcl.api.HMCLApi.EVENT_BUS]
|
||||||
|
* @param source [org.jackhuang.hmcl.core.version.MinecraftVersionManager]
|
||||||
|
* @param version the version id.
|
||||||
|
* @author huangyuhui
|
||||||
|
*/
|
||||||
|
class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(source)
|
@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.game
|
package org.jackhuang.hmcl.game
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import org.jackhuang.hmcl.event.*
|
||||||
import org.jackhuang.hmcl.util.GSON
|
import org.jackhuang.hmcl.util.GSON
|
||||||
import org.jackhuang.hmcl.util.LOG
|
import org.jackhuang.hmcl.util.LOG
|
||||||
import org.jackhuang.hmcl.util.fromJson
|
import org.jackhuang.hmcl.util.fromJson
|
||||||
@ -27,7 +28,7 @@ import java.io.IOException
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
open class DefaultGameRepository(val baseDirectory: File): GameRepository {
|
open class DefaultGameRepository(var baseDirectory: File): GameRepository {
|
||||||
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
|
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
|
||||||
|
|
||||||
override fun hasVersion(id: String) = versions.containsKey(id)
|
override fun hasVersion(id: String) = versions.containsKey(id)
|
||||||
@ -81,9 +82,8 @@ open class DefaultGameRepository(val baseDirectory: File): GameRepository {
|
|||||||
return file.deleteRecursively()
|
return file.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun refreshVersionsImpl() {
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun refreshVersions() {
|
|
||||||
versions.clear()
|
versions.clear()
|
||||||
|
|
||||||
if (ClassicVersion.hasClassicVersion(baseDirectory)) {
|
if (ClassicVersion.hasClassicVersion(baseDirectory)) {
|
||||||
@ -123,7 +123,16 @@ open class DefaultGameRepository(val baseDirectory: File): GameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
versions[id] = version
|
versions[id] = version
|
||||||
|
EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
final override fun refreshVersions() {
|
||||||
|
EVENT_BUS.fireEvent(RefreshingVersionsEvent(this))
|
||||||
|
refreshVersionsImpl()
|
||||||
|
EVENT_BUS.fireEvent(RefreshedVersionsEvent(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAssetIndex(assetId: String): AssetIndex {
|
override fun getAssetIndex(assetId: String): AssetIndex {
|
||||||
|
@ -95,6 +95,14 @@ interface GameRepository : VersionProvider {
|
|||||||
*/
|
*/
|
||||||
fun getVersionJar(version: Version): File
|
fun getVersionJar(version: Version): File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get minecraft jar
|
||||||
|
*
|
||||||
|
* @param version version id
|
||||||
|
* @return the minecraft jar
|
||||||
|
*/
|
||||||
|
fun getVersionJar(version: String): File = getVersionJar(getVersion(version).resolve(this))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename given version to new name.
|
* Rename given version to new name.
|
||||||
*
|
*
|
||||||
|
139
HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.kt
Normal file
139
HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.kt
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.game
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.closeQuietly
|
||||||
|
import org.jackhuang.hmcl.util.readFullyAsByteArray
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
private fun lessThan32(b: ByteArray, x: Int): Int {
|
||||||
|
var x = x
|
||||||
|
while (x < b.size) {
|
||||||
|
if (b[x] < 32)
|
||||||
|
return x
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchArray(a: ByteArray, b: ByteArray): Int {
|
||||||
|
for (i in 0..a.size - b.size - 1) {
|
||||||
|
var j = 1
|
||||||
|
for (k in b.indices) {
|
||||||
|
if (b[k] == a[i + k])
|
||||||
|
continue
|
||||||
|
j = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (j != 0)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun getVersionOfOldMinecraft(file: ZipFile, entry: ZipEntry): String? {
|
||||||
|
val tmp = file.getInputStream(entry).readFullyAsByteArray()
|
||||||
|
|
||||||
|
val bytes = "Minecraft Minecraft ".toByteArray(Charsets.US_ASCII)
|
||||||
|
var j = matchArray(tmp, bytes)
|
||||||
|
if (j < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val i = j + bytes.size
|
||||||
|
j = lessThan32(tmp, i)
|
||||||
|
|
||||||
|
if (j < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val ver = String(tmp, i, j - i, Charsets.US_ASCII)
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun getVersionOfNewMinecraft(file: ZipFile, entry: ZipEntry): String? {
|
||||||
|
val tmp = file.getInputStream(entry).readFullyAsByteArray()
|
||||||
|
|
||||||
|
var str = "-server.txt".toByteArray(charset("ASCII"))
|
||||||
|
var j = matchArray(tmp, str)
|
||||||
|
if (j < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var i = j + str.size
|
||||||
|
i += 11
|
||||||
|
j = lessThan32(tmp, i)
|
||||||
|
if (j < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val result = String(tmp, i, j - i, Charsets.US_ASCII)
|
||||||
|
|
||||||
|
val ch = result[0]
|
||||||
|
// 1.8.1+
|
||||||
|
if (ch < '0' || ch > '9') {
|
||||||
|
str = "Can't keep up! Did the system time change, or is the server overloaded?".toByteArray(charset("ASCII"))
|
||||||
|
j = matchArray(tmp, str)
|
||||||
|
if (j < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
i = -1
|
||||||
|
while (j > 0) {
|
||||||
|
if (tmp[j] in 48..57) {
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if (i == -1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var k = i
|
||||||
|
if (tmp[i + 1] >= 'a'.toInt() && tmp[i + 1] <= 'z'.toInt())
|
||||||
|
i++
|
||||||
|
while (tmp[k] in 48..57 || tmp[k] == '-'.toByte() || tmp[k] == '.'.toByte() || tmp[k] >= 97 && tmp[k] <= 'z'.toByte())
|
||||||
|
k--
|
||||||
|
k++
|
||||||
|
return String(tmp, k, i - k + 1, Charsets.US_ASCII)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun minecraftVersion(file: File?): String? {
|
||||||
|
if (file == null || !file.isFile || !file.canRead()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var f: ZipFile? = null
|
||||||
|
try {
|
||||||
|
f = ZipFile(file)
|
||||||
|
val minecraft = f
|
||||||
|
.getEntry("net/minecraft/client/Minecraft.class")
|
||||||
|
if (minecraft != null)
|
||||||
|
return getVersionOfOldMinecraft(f, minecraft)
|
||||||
|
val main = f.getEntry("net/minecraft/client/main/Main.class")
|
||||||
|
val minecraftserver = f.getEntry("net/minecraft/server/MinecraftServer.class")
|
||||||
|
if (main != null && minecraftserver != null)
|
||||||
|
return getVersionOfNewMinecraft(f, minecraftserver)
|
||||||
|
return null
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
f?.closeQuietly()
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
import org.jackhuang.hmcl.game.Library
|
import org.jackhuang.hmcl.game.Library
|
||||||
|
import java.io.File
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -40,6 +41,8 @@ val GSON: Gson = GsonBuilder()
|
|||||||
.registerTypeAdapter(Library::class.java, Library)
|
.registerTypeAdapter(Library::class.java, Library)
|
||||||
.registerTypeAdapter(Date::class.java, DateTypeAdapter)
|
.registerTypeAdapter(Date::class.java, DateTypeAdapter)
|
||||||
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
|
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
|
||||||
|
.registerTypeAdapter(Platform::class.java, Platform)
|
||||||
|
.registerTypeAdapter(File::class.java, FileTypeAdapter)
|
||||||
.registerTypeAdapterFactory(ValidationTypeAdapterFactory)
|
.registerTypeAdapterFactory(ValidationTypeAdapterFactory)
|
||||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory)
|
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory)
|
||||||
.create()
|
.create()
|
||||||
@ -48,6 +51,14 @@ inline fun <reified T> typeOf(): Type = object : TypeToken<T>() {}.type
|
|||||||
|
|
||||||
inline fun <reified T> Gson.fromJson(json: String): T? = fromJson<T>(json, T::class.java)
|
inline fun <reified T> Gson.fromJson(json: String): T? = fromJson<T>(json, T::class.java)
|
||||||
|
|
||||||
|
inline fun <reified T> Gson.fromJsonQuietly(json: String): T? {
|
||||||
|
try {
|
||||||
|
return fromJson<T>(json)
|
||||||
|
} catch (json: JsonParseException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the json object's fields automatically filled by Gson are in right format.
|
* Check if the json object's fields automatically filled by Gson are in right format.
|
||||||
*/
|
*/
|
||||||
@ -189,4 +200,17 @@ object DateTypeAdapter : JsonSerializer<Date>, JsonDeserializer<Date> {
|
|||||||
return result.substring(0, 22) + ":" + result.substring(22)
|
return result.substring(0, 22) + ":" + result.substring(22)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object FileTypeAdapter : JsonSerializer<File>, JsonDeserializer<File> {
|
||||||
|
override fun serialize(src: File?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||||
|
if (src == null) return JsonNull.INSTANCE
|
||||||
|
else return JsonPrimitive(src.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): File? {
|
||||||
|
if (json == null) return null
|
||||||
|
else return File(json.asString)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -17,12 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util
|
package org.jackhuang.hmcl.util
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
data class JavaVersion internal constructor(
|
data class JavaVersion internal constructor(
|
||||||
|
@SerializedName("location")
|
||||||
val binary: File,
|
val binary: File,
|
||||||
val version: Int,
|
val version: Int,
|
||||||
val platform: Platform) : Serializable
|
val platform: Platform) : Serializable
|
||||||
@ -74,8 +76,8 @@ data class JavaVersion internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getJavaFile(home: File): File {
|
fun getJavaFile(home: File): File {
|
||||||
var path = home.resolve("bin")
|
val path = home.resolve("bin")
|
||||||
var javaw = path.resolve("javaw.exe")
|
val javaw = path.resolve("javaw.exe")
|
||||||
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
|
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
|
||||||
return javaw
|
return javaw
|
||||||
else
|
else
|
||||||
|
@ -17,12 +17,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util
|
package org.jackhuang.hmcl.util
|
||||||
|
|
||||||
|
import com.google.gson.*
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
enum class Platform(val bit: String) {
|
enum class Platform(val bit: String) {
|
||||||
BIT_32("32"),
|
BIT_32("32"),
|
||||||
BIT_64("64"),
|
BIT_64("64"),
|
||||||
UNKNOWN("unknown");
|
UNKNOWN("unknown");
|
||||||
|
|
||||||
companion object {
|
companion object Serializer: JsonSerializer<Platform>, JsonDeserializer<Platform> {
|
||||||
|
|
||||||
|
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Platform? {
|
||||||
|
if (json == null) return null
|
||||||
|
return when (json.asInt) {
|
||||||
|
0 -> BIT_32
|
||||||
|
1 -> BIT_64
|
||||||
|
else -> UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(src: Platform?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
|
||||||
|
if (src == null) return null
|
||||||
|
return when (src) {
|
||||||
|
BIT_32 -> JsonPrimitive(0)
|
||||||
|
BIT_64 -> JsonPrimitive(1)
|
||||||
|
UNKNOWN -> JsonPrimitive(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val PLATFORM: Platform by lazy {
|
val PLATFORM: Platform by lazy {
|
||||||
if (IS_64_BIT) BIT_64 else BIT_32
|
if (IS_64_BIT) BIT_64 else BIT_32
|
||||||
|
@ -40,13 +40,19 @@ class SimpleMultimap<K, V>(val maper: () -> MutableMap<K, Collection<V>>, val va
|
|||||||
valuesImpl += value
|
valuesImpl += value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(key: K): Collection<V>? {
|
fun removeAll(key: K): Collection<V>? {
|
||||||
val result = map.remove(key)
|
val result = map.remove(key)
|
||||||
if (result != null)
|
if (result != null)
|
||||||
valuesImpl.removeAll(result)
|
valuesImpl.removeAll(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun remove(value: V) {
|
||||||
|
map.values.forEach {
|
||||||
|
it.remove(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
map.clear()
|
map.clear()
|
||||||
valuesImpl.clear()
|
valuesImpl.clear()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user