mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-01-30 14:39:56 +08:00
Launching 1.13
This commit is contained in:
parent
4bde509708
commit
d9225b9529
48
HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt
Normal file
48
HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import com.google.gson.*
|
||||
import java.lang.reflect.Type
|
||||
|
||||
interface Argument {
|
||||
|
||||
/**
|
||||
* Parse this argument in form: ${key name} or simply a string.
|
||||
*
|
||||
* @param keys the parse map
|
||||
* @param features the map that contains some features such as 'is_demo_user', 'has_custom_resolution'
|
||||
* @return parsed argument element, empty if this argument is ignored and will not be added.
|
||||
*/
|
||||
fun toString(keys: Map<String, String>, features: Map<String, Boolean>): List<String>
|
||||
|
||||
companion object Serializer : JsonDeserializer<Argument>, JsonSerializer<Argument> {
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Argument =
|
||||
if (json.isJsonPrimitive)
|
||||
StringArgument(json.asString)
|
||||
else
|
||||
context.deserialize(json, RuledArgument::class.java)
|
||||
|
||||
override fun serialize(src: Argument, typeOfSrc: Type, context: JsonSerializationContext): JsonElement =
|
||||
when (src) {
|
||||
is StringArgument -> JsonPrimitive(src.argument)
|
||||
is RuledArgument -> context.serialize(src, RuledArgument::class.java)
|
||||
else -> throw AssertionError("Unrecognized argument type: $src")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.util.OS
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule.*
|
||||
|
||||
class Arguments @JvmOverloads constructor(
|
||||
@SerializedName("game")
|
||||
val game: List<Argument>? = null,
|
||||
@SerializedName("jvm")
|
||||
val jvm: List<Argument>? = null
|
||||
) {
|
||||
companion object {
|
||||
fun parseStringArguments(arguments: List<String>, keys: Map<String, String>, features: Map<String, Boolean> = emptyMap()): List<String> {
|
||||
return arguments.flatMap { StringArgument(it).toString(keys, features) }
|
||||
}
|
||||
|
||||
fun parseArguments(arguments: List<Argument>, keys: Map<String, String>, features: Map<String, Boolean> = emptyMap()): List<String> {
|
||||
return arguments.flatMap { it.toString(keys, features) }
|
||||
}
|
||||
|
||||
val DEFAULT_JVM_ARGUMENTS = listOf(
|
||||
RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.OSX))), listOf("-XstartOnFirstThread")),
|
||||
RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.WINDOWS))), listOf("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump")),
|
||||
RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.WINDOWS, "^10\\."))), listOf("-Dos.name=Windows 10", "-Dos.version=10.0")),
|
||||
StringArgument("-Djava.library.path=\${natives_directory}"),
|
||||
StringArgument("-Dminecraft.launcher.brand=\${launcher_name}"),
|
||||
StringArgument("-Dminecraft.launcher.version=\${launcher_version}"),
|
||||
StringArgument("-cp"),
|
||||
StringArgument("\${classpath}")
|
||||
)
|
||||
|
||||
val DEFAULT_GAME_ARGUMENTS = listOf(
|
||||
RuledArgument(listOf(CompatibilityRule(Action.ALLOW, features = mapOf("has_custom_resolution" to true))), listOf("--width", "\${resolution_width}", "--height", "\${resolution_height}"))
|
||||
)
|
||||
}
|
||||
}
|
@ -26,18 +26,28 @@ import java.util.regex.Pattern
|
||||
@Immutable
|
||||
data class CompatibilityRule(
|
||||
val action: Action = CompatibilityRule.Action.ALLOW,
|
||||
val os: OSRestriction? = null
|
||||
val os: OSRestriction? = null,
|
||||
val features: Map<String, Boolean>? = null
|
||||
) {
|
||||
|
||||
val appliedAction: Action? get() = if (os != null && !os.allow()) null else action
|
||||
fun getAppliedAction(supportedFeatures: Map<String, Boolean>): Action? {
|
||||
if (os != null && !os.allow()) return null
|
||||
if (features != null) {
|
||||
features.entries.forEach {
|
||||
if (supportedFeatures[it.key] != it.value)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun appliesToCurrentEnvironment(rules: Collection<CompatibilityRule>?): Boolean {
|
||||
fun appliesToCurrentEnvironment(rules: Collection<CompatibilityRule>?, features: Map<String, Boolean> = emptyMap()): Boolean {
|
||||
if (rules == null)
|
||||
return true
|
||||
var action = CompatibilityRule.Action.DISALLOW
|
||||
for (rule in rules) {
|
||||
val thisAction = rule.appliedAction
|
||||
val thisAction = rule.getAppliedAction(features)
|
||||
if (thisAction != null) action = thisAction
|
||||
}
|
||||
return action == CompatibilityRule.Action.ALLOW
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import com.google.gson.*
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.jackhuang.hmcl.util.typeOf
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
class RuledArgument @JvmOverloads constructor(
|
||||
val rules: List<CompatibilityRule>? = null,
|
||||
val value: List<String>? = null) : Argument {
|
||||
|
||||
override fun toString(keys: Map<String, String>, features: Map<String, Boolean>): List<String> =
|
||||
if (CompatibilityRule.appliesToCurrentEnvironment(rules))
|
||||
value?.map { StringArgument(it).toString(keys, features).single() } ?: emptyList()
|
||||
else
|
||||
emptyList()
|
||||
|
||||
companion object Serializer : JsonSerializer<RuledArgument>, JsonDeserializer<RuledArgument> {
|
||||
override fun serialize(src: RuledArgument, typeOfSrc: Type, context: JsonSerializationContext) =
|
||||
JsonObject().apply {
|
||||
add("rules", context.serialize(src.rules))
|
||||
add("value", context.serialize(src.value))
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): RuledArgument {
|
||||
val obj = json.asJsonObject
|
||||
return RuledArgument(
|
||||
rules = context.deserialize(obj["rules"], typeOf<List<CompatibilityRule>>()),
|
||||
value = if (obj["value"].isJsonPrimitive)
|
||||
listOf(obj["value"].asString)
|
||||
else
|
||||
context.deserialize(obj["value"], typeOf<List<String>>())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class StringArgument(var argument: String) : Argument {
|
||||
|
||||
override fun toString(keys: Map<String, String>, features: Map<String, Boolean>): List<String> {
|
||||
var res = argument
|
||||
val pattern = Pattern.compile("\\$\\{(.*?)\\}")
|
||||
val m = pattern.matcher(argument)
|
||||
while (m.find()) {
|
||||
val entry = m.group()
|
||||
res = res.replace(entry, keys.getOrDefault(entry, entry))
|
||||
}
|
||||
return Collections.singletonList(res)
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,8 @@ import java.util.*
|
||||
open class Version(
|
||||
@SerializedName("minecraftArguments")
|
||||
val minecraftArguments: String? = null,
|
||||
@SerializedName("arguments")
|
||||
val arguments: Arguments? = null,
|
||||
@SerializedName("mainClass")
|
||||
val mainClass: String? = null,
|
||||
@SerializedName("time")
|
||||
@ -138,6 +140,7 @@ open class Version(
|
||||
|
||||
fun copy(
|
||||
minecraftArguments: String? = this.minecraftArguments,
|
||||
arguments: Arguments? = this.arguments,
|
||||
mainClass: String? = this.mainClass,
|
||||
time: Date = this.time,
|
||||
releaseTime: Date = this.releaseTime,
|
||||
@ -153,6 +156,7 @@ open class Version(
|
||||
downloads: Map<DownloadType, DownloadInfo>? = this.downloads,
|
||||
logging: Map<DownloadType, LoggingInfo>? = this.logging) =
|
||||
Version(minecraftArguments,
|
||||
arguments,
|
||||
mainClass,
|
||||
time,
|
||||
id,
|
||||
|
@ -18,10 +18,7 @@
|
||||
package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.game.DownloadType
|
||||
import org.jackhuang.hmcl.game.GameException
|
||||
import org.jackhuang.hmcl.game.GameRepository
|
||||
import org.jackhuang.hmcl.game.LaunchOptions
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
@ -48,6 +45,9 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
throw NullPointerException("Version main class can not be null")
|
||||
}
|
||||
|
||||
protected open val defaultJVMArguments = Arguments.DEFAULT_JVM_ARGUMENTS
|
||||
protected open val defaultGameArguments = Arguments.DEFAULT_GAME_ARGUMENTS
|
||||
|
||||
/**
|
||||
* Note: the [account] must have logged in when calling this property
|
||||
*/
|
||||
@ -84,9 +84,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
}
|
||||
}
|
||||
|
||||
if (OS.CURRENT_OS == OS.WINDOWS)
|
||||
res.add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump")
|
||||
else
|
||||
if (OS.CURRENT_OS != OS.WINDOWS)
|
||||
res.add("-Duser.home=${options.gameDir.parent}")
|
||||
|
||||
if (options.java.version >= JavaVersion.JAVA_7)
|
||||
@ -114,9 +112,6 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true")
|
||||
}
|
||||
|
||||
// Classpath
|
||||
res.add("-Djava.library.path=${native.absolutePath}")
|
||||
|
||||
val lateload = LinkedList<File>()
|
||||
val classpath = StringBuilder()
|
||||
for (library in version.libraries)
|
||||
@ -135,32 +130,21 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
if (!jar.exists() || !jar.isFile)
|
||||
throw GameException("Minecraft jar does not exist")
|
||||
classpath.append(jar.absolutePath)
|
||||
res.add("-cp")
|
||||
res.add(classpath.toString())
|
||||
|
||||
// Main Class
|
||||
res.add(version.mainClass!!)
|
||||
|
||||
// Provided Minecraft arguments
|
||||
val gameAssets = repository.getActualAssetDirectory(version.id, version.actualAssetIndex.id)
|
||||
val configuration = getConfigurations()
|
||||
configuration["\${classpath}"] = classpath.toString()
|
||||
configuration["\${natives_directory}"] = native.absolutePath
|
||||
configuration["\${game_assets}"] = gameAssets.absolutePath
|
||||
configuration["\${assets_root}"] = gameAssets.absolutePath
|
||||
|
||||
version.minecraftArguments!!.tokenize().forEach { line ->
|
||||
res.add(line
|
||||
.replace("\${auth_player_name}", account.username)
|
||||
.replace("\${auth_session}", account.authToken)
|
||||
.replace("\${auth_access_token}", account.authToken)
|
||||
.replace("\${auth_uuid}", account.userId)
|
||||
.replace("\${version_name}", options.versionName ?: version.id)
|
||||
.replace("\${profile_name}", options.profileName ?: "Minecraft")
|
||||
.replace("\${version_type}", version.type.id)
|
||||
.replace("\${game_directory}", repository.getRunDirectory(version.id).absolutePath)
|
||||
.replace("\${game_assets}", gameAssets.absolutePath)
|
||||
.replace("\${assets_root}", gameAssets.absolutePath)
|
||||
.replace("\${user_type}", account.userType.toString().toLowerCase())
|
||||
.replace("\${assets_index_name}", version.actualAssetIndex.id)
|
||||
.replace("\${user_properties}", account.userProperties)
|
||||
)
|
||||
}
|
||||
res.addAll(Arguments.parseArguments(version.arguments?.jvm ?: defaultJVMArguments, configuration))
|
||||
res.add(version.mainClass!!)
|
||||
|
||||
val features = getFeatures()
|
||||
res.addAll(Arguments.parseArguments(version.arguments?.game ?: defaultGameArguments, configuration, features))
|
||||
res.addAll(Arguments.parseStringArguments(version.minecraftArguments?.tokenize()?.toList() ?: emptyList(), configuration))
|
||||
|
||||
// Optional Minecraft arguments
|
||||
if (options.height != null && options.height != 0 && options.width != null && options.width != 0) {
|
||||
@ -222,6 +206,24 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
}
|
||||
}
|
||||
|
||||
open fun getConfigurations(): MutableMap<String, String> = mutableMapOf(
|
||||
"\${auth_player_name}" to account.username,
|
||||
"\${auth_session}" to account.authToken,
|
||||
"\${auth_access_token}" to account.authToken,
|
||||
"\${auth_uuid}" to account.userId,
|
||||
"\${version_name}" to (options.versionName ?: version.id),
|
||||
"\${profile_name}" to (options.profileName ?: "Minecraft"),
|
||||
"\${version_type}" to version.type.id,
|
||||
"\${game_directory}" to repository.getRunDirectory(version.id).absolutePath,
|
||||
"\${user_type}" to account.userType.toString().toLowerCase(),
|
||||
"\${assets_index_name}" to version.actualAssetIndex.id,
|
||||
"\${user_properties}" to account.userProperties
|
||||
)
|
||||
|
||||
open fun getFeatures(): MutableMap<String, Boolean> = mutableMapOf(
|
||||
"has_custom_resolution" to (options.height != null && options.height != 0 && options.width != null && options.width != 0)
|
||||
)
|
||||
|
||||
override fun launch(): ManagedProcess {
|
||||
|
||||
// To guarantee that when failed to generate code, we will not call precalled command
|
||||
@ -232,8 +234,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
|
||||
try {
|
||||
val process = Runtime.getRuntime().exec(options.precalledCommand)
|
||||
if (process.isAlive)
|
||||
process.waitFor()
|
||||
if (process.isAlive)
|
||||
process.waitFor()
|
||||
} catch (e: IOException) {
|
||||
// TODO: alert precalledCommand is wrong.
|
||||
// rethrow InterruptedException
|
||||
@ -301,7 +303,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
processListener.setProcess(managedProcess)
|
||||
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() }
|
||||
managedProcess.relatedThreads += logHandler
|
||||
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { logHandler.newLine(it) } )::run)
|
||||
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { logHandler.newLine(it) })::run)
|
||||
managedProcess.relatedThreads += stdout
|
||||
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run)
|
||||
managedProcess.relatedThreads += stderr
|
||||
|
@ -22,7 +22,9 @@ import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import org.jackhuang.hmcl.game.Argument
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.game.RuledArgument
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Type
|
||||
@ -36,6 +38,8 @@ val GSON: Gson = GsonBuilder()
|
||||
.enableComplexMapKeySerialization()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(Library::class.java, Library)
|
||||
.registerTypeAdapter(Argument::class.java, Argument)
|
||||
.registerTypeAdapter(RuledArgument::class.java, RuledArgument)
|
||||
.registerTypeAdapter(Date::class.java, DateTypeAdapter)
|
||||
.registerTypeAdapter(UUID::class.java, UUIDTypeAdapter)
|
||||
.registerTypeAdapter(Platform::class.java, Platform)
|
||||
|
@ -20,7 +20,7 @@ group 'org.jackhuang'
|
||||
version '3.0'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.4-2'
|
||||
ext.kotlin_version = '1.1.4-4'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
Loading…
Reference in New Issue
Block a user