Launching 1.13

This commit is contained in:
huangyuhui 2017-11-03 13:38:38 +08:00
parent 4bde509708
commit d9225b9529
9 changed files with 252 additions and 40 deletions

View 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")
}
}
}

View File

@ -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}"))
)
}
}

View File

@ -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

View File

@ -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>>())
)
}
}
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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()