yggdrasil implementation, player data: read player properties correct

This commit is contained in:
Bixilon 2021-12-13 20:09:02 +01:00
parent b55d24ca89
commit d0307e9fb2
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
24 changed files with 198 additions and 64 deletions

View File

@ -444,5 +444,10 @@
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.0-4</version>
</dependency>
</dependencies>
</project>

View File

@ -122,6 +122,8 @@ object Minosoft {
Util.forceClassInit(Eros::class.java)
}
taskWorker += Task(identifier = StartupTasks.LOAD_YGGDRASIL, executor = { YggdrasilUtil.load() })
taskWorker.work(START_UP_LATCH)

View File

@ -19,7 +19,7 @@ import de.bixilon.minosoft.data.entities.Poses
import de.bixilon.minosoft.data.entities.entities.EntityMetaDataFunction
import de.bixilon.minosoft.data.entities.entities.LivingEntity
import de.bixilon.minosoft.data.player.Arms
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.player.tab.TabListItem
import de.bixilon.minosoft.data.registries.entities.EntityType
import de.bixilon.minosoft.data.world.World
@ -35,7 +35,7 @@ abstract class PlayerEntity(
position: Vec3d = Vec3d.EMPTY,
rotation: EntityRotation = EntityRotation(0.0, 0.0),
name: String = "TBA",
properties: Map<String, PlayerProperty> = mapOf(),
properties: PlayerProperties = PlayerProperties(),
var tabListItem: TabListItem = TabListItem(name = name, gamemode = Gamemodes.SURVIVAL, properties = properties),
) : LivingEntity(connection, entityType, position, rotation) {
override val dimensions: Vec2

View File

@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.entities.entities.player
import de.bixilon.minosoft.data.abilities.Gamemodes
import de.bixilon.minosoft.data.entities.EntityRotation
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.player.tab.TabListItem
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.registries.entities.EntityFactory
@ -30,7 +30,7 @@ class RemotePlayerEntity(
position: Vec3d = Vec3d.EMPTY,
rotation: EntityRotation = EntityRotation(0.0, 0.0),
name: String = "TBA",
properties: Map<String, PlayerProperty> = mapOf(),
properties: PlayerProperties = PlayerProperties(),
tabListItem: TabListItem = TabListItem(name = name, gamemode = Gamemodes.SURVIVAL, properties = properties),
) : PlayerEntity(connection, entityType, position, rotation, name, properties, tabListItem) {

View File

@ -1,26 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.player
class PlayerProperty(
val key: String,
val value: String,
val signature: String? = null,
) {
val isSigned: Boolean
get() = signature != null // ToDo check signature
override fun toString(): String {
return "$key: $value"
}
}

View File

@ -0,0 +1,7 @@
package de.bixilon.minosoft.data.player.properties
import de.bixilon.minosoft.data.player.properties.textures.PlayerTextures
class PlayerProperties(
val textures: PlayerTextures? = null,
)

View File

@ -0,0 +1,29 @@
package de.bixilon.minosoft.data.player.properties.textures
import de.bixilon.minosoft.util.KUtil.check
import java.net.URL
open class PlayerTexture(
val url: URL,
) {
init {
url.check()
check(urlMatches(url, ALLOWED_DOMAINS) && !urlMatches(url, BLOCKED_DOMAINS)) { "URL hostname is not allowed!" }
}
companion object {
private val ALLOWED_DOMAINS = arrayOf(".minecraft.net", ".mojang.com")
private val BLOCKED_DOMAINS = arrayOf("bugs.mojang.com", "education.minecraft.net", "feedback.minecraft.net")
private fun urlMatches(url: URL, domains: Array<String>): Boolean {
for (checkURL in domains) {
if (url.host.endsWith(checkURL)) {
return true
}
}
return false
}
}
}

View File

@ -0,0 +1,38 @@
package de.bixilon.minosoft.data.player.properties.textures
import com.fasterxml.jackson.module.kotlin.convertValue
import de.bixilon.minosoft.util.KUtil.toLong
import de.bixilon.minosoft.util.Util
import de.bixilon.minosoft.util.YggdrasilUtil
import de.bixilon.minosoft.util.json.Jackson
import de.bixilon.minosoft.util.nbt.tag.NBTUtil.compoundCast
import java.util.*
class PlayerTextures(
val name: String?,
val uuid: UUID?,
val date: Date?,
val skin: SkinPlayerTexture?,
val cape: PlayerTexture?,
val elytra: PlayerTexture?,
) {
companion object {
fun of(encoded: String, signature: String): PlayerTextures {
check(YggdrasilUtil.verify(encoded, signature)) { "Texture signature is invalid!" }
val json: Map<String, Any> = Jackson.MAPPER.readValue(Base64.getDecoder().decode(encoded), Jackson.JSON_MAP_TYPE)
// Data also contains `signatureRequired`
val textures = json["textures"]?.compoundCast()
return PlayerTextures(
name = json["profileName"]?.toString(),
uuid = json["profileId"]?.toString()?.let { Util.getUUIDFromString(it) },
date = json["timestamp"]?.toLong()?.let { Date(it) },
skin = textures?.get("SKIN")?.compoundCast()?.let { return@let Jackson.MAPPER.convertValue(it) },
cape = textures?.get("CAPE")?.compoundCast()?.let { return@let Jackson.MAPPER.convertValue(it) },
elytra = textures?.get("ELYTRA")?.compoundCast()?.let { return@let Jackson.MAPPER.convertValue(it) },
)
}
}
}

View File

@ -0,0 +1,9 @@
package de.bixilon.minosoft.data.player.properties.textures
import de.bixilon.minosoft.data.player.properties.textures.metadata.SkinMetadata
import java.net.URL
class SkinPlayerTexture(
url: URL,
val metadata: SkinMetadata = SkinMetadata(),
) : PlayerTexture(url = url)

View File

@ -0,0 +1,5 @@
package de.bixilon.minosoft.data.player.properties.textures.metadata
data class SkinMetadata(
val model: SkinModel = SkinModel.NORMAL,
)

View File

@ -0,0 +1,7 @@
package de.bixilon.minosoft.data.player.properties.textures.metadata
enum class SkinModel {
SLIM,
NORMAL,
;
}

View File

@ -14,7 +14,7 @@
package de.bixilon.minosoft.data.player.tab
import de.bixilon.minosoft.data.abilities.Gamemodes
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.scoreboard.Team
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.util.KUtil.nullCompare
@ -24,7 +24,7 @@ data class TabListItem(
var ping: Int = -1,
var gamemode: Gamemodes = Gamemodes.SURVIVAL,
var displayName: ChatComponent = ChatComponent.of(name),
var properties: Map<String, PlayerProperty> = mutableMapOf(),
var properties: PlayerProperties = PlayerProperties(),
var team: Team? = null,
) : Comparable<TabListItem> {
val tabDisplayName: ChatComponent

View File

@ -14,7 +14,7 @@
package de.bixilon.minosoft.data.player.tab
import de.bixilon.minosoft.data.abilities.Gamemodes
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.scoreboard.Team
import de.bixilon.minosoft.data.text.ChatComponent
@ -24,7 +24,7 @@ data class TabListItemData(
var gamemode: Gamemodes? = null,
var hasDisplayName: Boolean? = null,
var displayName: ChatComponent? = null,
val properties: Map<String, PlayerProperty>? = null,
val properties: PlayerProperties? = null,
var remove: Boolean = false, // used for legacy tab list
var team: Team? = null,
var removeFromTeam: Boolean = false,

View File

@ -20,6 +20,8 @@ import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.toVec2i
import de.bixilon.minosoft.util.KUtil.mapCast
import de.bixilon.minosoft.util.KUtil.toInt
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import glm_.vec2.Vec2
import glm_.vec2.Vec2i
class HUDAtlasManager(private val hudRenderer: HUDRenderer) {
private lateinit var elements: Map<ResourceLocation, HUDAtlasElement>
@ -79,8 +81,8 @@ class HUDAtlasManager(private val hudRenderer: HUDRenderer) {
fun postInit() {
for (element in elements.values) {
element.uvStart = element.texture.singlePixelSize * element.start
element.uvEnd = element.texture.singlePixelSize * element.end
element.uvStart = ATLAS_SINGLE_PIXEL_SIZE * element.start
element.uvEnd = ATLAS_SINGLE_PIXEL_SIZE * element.end
}
}
@ -94,5 +96,8 @@ class HUDAtlasManager(private val hudRenderer: HUDRenderer) {
companion object {
private val ATLAS_DATA = "minosoft:mapping/atlas.json".toResourceLocation()
private val ATLAS_SIZE = Vec2i(256, 256)
private val ATLAS_SINGLE_PIXEL_SIZE = Vec2(1.0f) / ATLAS_SIZE
}
}

View File

@ -198,8 +198,8 @@ class OpenGLTextureArray(
companion object {
val TEXTURE_RESOLUTION_ID_MAP = intArrayOf(16, 32, 64, 128, 256, 512, 1024) // A 12x12 texture will be saved in texture id 0 (in 0 are only 16x16 textures). Animated textures get split
const val TEXTURE_MAX_RESOLUTION = 1024
val TEXTURE_RESOLUTION_ID_MAP = intArrayOf(16, 32, 64, 128, 256, 512, TEXTURE_MAX_RESOLUTION) // A 12x12 texture will be saved in texture id 0 (in 0 are only 16x16 textures). Animated textures get split
const val MAX_MIPMAP_LEVELS = 5
}
}

View File

@ -16,7 +16,7 @@ import de.bixilon.minosoft.data.entities.EntityRotation
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.RemotePlayerEntity
import de.bixilon.minosoft.data.entities.meta.EntityMetaData
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.modding.event.events.EntitySpawnEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
@ -38,15 +38,11 @@ class PlayerEntitySpawnS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
entityId = buffer.readVarInt()
var name = "TBA"
val properties: MutableMap<String, PlayerProperty> = mutableMapOf()
var properties = PlayerProperties()
if (buffer.versionId < ProtocolVersions.V_14W21A) {
name = buffer.readString()
entityUUID = buffer.readUUIDString()
val length = buffer.readVarInt()
for (i in 0 until length) {
val property = PlayerProperty(buffer.readString(), buffer.readString(), buffer.readString())
properties[property.key] = property
}
properties = buffer.readPlayerProperties()
} else {
entityUUID = buffer.readUUID()
}

View File

@ -14,7 +14,6 @@ package de.bixilon.minosoft.protocol.packets.s2c.play
import de.bixilon.minosoft.data.abilities.Gamemodes
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.player.PlayerProperty
import de.bixilon.minosoft.data.player.tab.TabListItem
import de.bixilon.minosoft.data.player.tab.TabListItemData
import de.bixilon.minosoft.modding.event.events.TabListEntryChangeEvent
@ -60,15 +59,7 @@ class TabListDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
when (action) {
TabListItemActions.ADD -> {
val name = buffer.readString()
val playerProperties: MutableMap<String, PlayerProperty> = mutableMapOf()
for (index in 0 until buffer.readVarInt()) {
val property = PlayerProperty(
buffer.readString(),
buffer.readString(),
buffer.readOptional { buffer.readString() },
)
playerProperties[property.key] = property
}
val properties = buffer.readPlayerProperties()
val gamemode = Gamemodes[buffer.readVarInt()]
val ping = buffer.readVarInt()
val hasDisplayName = buffer.readBoolean()
@ -79,7 +70,7 @@ class TabListDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
}
data = TabListItemData(
name = name,
properties = playerProperties,
properties = properties,
gamemode = gamemode,
ping = ping,
hasDisplayName = hasDisplayName,

View File

@ -14,6 +14,8 @@ package de.bixilon.minosoft.protocol.protocol
import de.bixilon.minosoft.data.entities.meta.EntityMetaData
import de.bixilon.minosoft.data.inventory.ItemStack
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.player.properties.textures.PlayerTextures
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.particle.ParticleType
import de.bixilon.minosoft.data.registries.particle.data.BlockParticleData
@ -214,4 +216,27 @@ class PlayInByteBuffer : InByteBuffer {
fun readEntityIdArray(length: Int = readVarInt()): Array<Int> {
return readArray(length) { readEntityId() }
}
fun readPlayerProperties(): PlayerProperties {
var textures: PlayerTextures? = null
for (i in 0 until readVarInt()) {
val name = readString()
val value = readString()
val signature = if (versionId < V_14W21A) {
readString()
} else {
readOptional { readString() }
}
when (name) {
"textures" -> {
check(textures == null) { "Textures duplicated" }
textures = PlayerTextures.of(value, signature ?: throw IllegalArgumentException("Texture data needs to be signed!"))
}
}
}
return PlayerProperties(
textures = textures,
)
}
}

View File

@ -33,6 +33,7 @@ import glm_.vec4.Vec4t
import sun.misc.Unsafe
import java.io.*
import java.lang.reflect.Field
import java.net.URL
import java.nio.ByteBuffer
import java.time.Instant
import java.util.*
@ -558,4 +559,9 @@ object KUtil {
val Locale.fullName: String
get() = language + "_" + country.ifEmpty { language.uppercase() }
fun URL.check() {
check(this.protocol == "http" || this.protocol == "https") { "Url is not a web address" }
}
}

View File

@ -0,0 +1,33 @@
package de.bixilon.minosoft.util
import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import java.security.KeyFactory
import java.security.PublicKey
import java.security.Signature
import java.security.spec.X509EncodedKeySpec
import java.util.*
object YggdrasilUtil {
lateinit var PUBLIC_KEY: PublicKey
private set
fun load() {
check(!this::PUBLIC_KEY.isInitialized) { "Already loaded!" }
val spec = X509EncodedKeySpec(Minosoft.MINOSOFT_ASSETS_MANAGER["minosoft:mojang/yggdrasil_session_pubkey.der".toResourceLocation()].readAllBytes())
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
PUBLIC_KEY = keyFactory.generatePublic(spec)
}
fun verify(data: ByteArray, signature: ByteArray): Boolean {
val signatureInstance = Signature.getInstance("SHA1withRSA")
signatureInstance.initVerify(PUBLIC_KEY)
signatureInstance.update(data)
return signatureInstance.verify(signature)
}
fun verify(data: String, signature: String): Boolean {
return verify(data.toByteArray(), Base64.getDecoder().decode(signature))
}
}

View File

@ -2,14 +2,19 @@ package de.bixilon.minosoft.util.json
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.type.MapType
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
object Jackson {
val MAPPER = ObjectMapper()
val MAPPER = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.build()
.registerModule(KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
@ -21,8 +26,6 @@ object Jackson {
.registerModule(ResourceLocationSerializer)
.registerModule(RGBColorSerializer)
.registerModule(ChatComponentColorSerializer)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
.setDefaultMergeable(true)

View File

@ -13,6 +13,8 @@
package de.bixilon.minosoft.util.nbt.tag
import de.bixilon.minosoft.util.KUtil.nullCast
object NBTUtil {
fun compound(): MutableMap<String, Any> {
@ -33,11 +35,7 @@ object NBTUtil {
}
fun Any?.compoundCast(): MutableMap<String, Any>? {
try {
return this as MutableMap<String, Any>
} catch (ignored: ClassCastException) {
}
return null
return this.nullCast()
}
fun Any?.asCompound(): MutableMap<String, Any> {

View File

@ -24,5 +24,6 @@ enum class StartupTasks {
INITIALIZE_JAVAFX,
X_START_ON_FIRST_THREAD_WARNING,
FILE_WATCHER,
LOAD_YGGDRASIL,
;
}