Skip to content

Commit

Permalink
Add an tModLoader that improves mini map support + sync to client
Browse files Browse the repository at this point in the history
Signed-off-by: Seppe Volkaerts <[email protected]>
  • Loading branch information
Cybermaxke committed Oct 26, 2024
1 parent af766a5 commit 04cf263
Show file tree
Hide file tree
Showing 23 changed files with 328 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_wrap_on_typing = true

[*.txt]
ij_wrap_on_typing = false

[{*.kts,*.kt}]
ij_kotlin_name_count_to_use_star_import = 100
ij_kotlin_name_count_to_use_star_import_for_members = 100
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@ nb-configuration.xml
!.gitkeep
config.yaml
*.launch
tmodloader/TerreAddon/.vs
tmodloader/TerreAddon/obj
tmodloader/TerreAddon/bin
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ internal object ProxyImpl : Proxy {

override var haProxy by config.property(ProxyConfigSpec.haProxy)

val tModLoaderSyncAddonMod by config
.property(ProxyConfigSpec.TModLoader.syncAddonMod)

val allowModdedClientsOnVanillaServers by config
.property(ProxyConfigSpec.TModLoader.allowModdedClientsOnVanillaServers)

var allowMultiplePlayersPerClientUniqueId by config
.property(ProxyConfigSpec.allowMultiplePlayersPerClientUniqueId)

Expand Down
49 changes: 49 additions & 0 deletions proxy/src/main/kotlin/org/lanternpowered/terre/impl/addon/Mod.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Terre
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) contributors
*
* This work is licensed under the terms of the MIT License (MIT). For
* a copy, see 'LICENSE.txt' or <https://opensource.org/licenses/MIT>.
*/
package org.lanternpowered.terre.impl.addon

import io.netty.buffer.Unpooled
import org.lanternpowered.terre.impl.network.buffer.readString
import java.io.InputStream

class Mod(
val name: String,
val version: String,
val tModLoaderVersion: String,
val hash: ByteArray,
val data: ByteArray
) {

companion object {

fun load(inputStream: InputStream): Mod {
val data = inputStream.readAllBytes()
val buf = Unpooled.wrappedBuffer(data)

val header = ByteArray(4)
buf.readBytes(header)
if (!"TMOD".toByteArray(Charsets.US_ASCII).contentEquals(header)) {
error("Not a mod file.")
}
val tModLoaderVersion = buf.readString()

val hash = ByteArray(20)
buf.readBytes(hash)

buf.skipBytes(256) // signature
buf.readIntLE() // data length for following data

val name = buf.readString()
val version = buf.readString()

return Mod(name, version, tModLoaderVersion, hash, data)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Terre
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) contributors
*
* This work is licensed under the terms of the MIT License (MIT). For
* a copy, see 'LICENSE.txt' or <https://opensource.org/licenses/MIT>.
*/
package org.lanternpowered.terre.impl.addon

object TerreAddon {

val mod = requireNotNull(TerreAddon::class.java.getResourceAsStream("/TerreAddon.tmod"))
.use { input -> Mod.load(input) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,18 @@ internal object ProxyConfigSpec : ConfigSpec("proxy") {
description = "If the local broadcast task is enabled."
)
}

object TModLoader : ConfigSpec("tModLoader") {

val allowModdedClientsOnVanillaServers by optional(
default = true,
description = "If tModLoader clients are allowed to connect to vanilla backing servers."
)

val syncAddonMod by optional(
default = true,
description = "If the TerreAddon mod should be automatically synced with the client without" +
" installing on backing servers."
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ internal object ProtocolRegistry {
/**
* All the allowed protocol translations.
*/
val allowedTranslations: List<ProtocolTranslation>
get() = mutableTranslations
fun allowedTranslations(allowModdedClientsOnVanillaServer: Boolean): List<ProtocolTranslation> {
if (!allowModdedClientsOnVanillaServer) {
// TODO: Expand if we have more tModLoader protocol versions
return mutableTranslations.filter { translation -> translation.from != ProtocolTModLoader }
}
return mutableTranslations
}

init {
register(ProtocolVersion.Vanilla.`1405`, Protocol230) // 230
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ import org.lanternpowered.terre.impl.network.packet.tmodloader.UpdateModConfigRe
import org.lanternpowered.terre.impl.network.packet.tmodloader.UpdateModConfigResponseEncoder

/**
* Protocol that will be used for tModLoader when vanilla clients aren't allowed.
*
* ModNet.AllowVanillaClients = false
* Protocol that will be used for tModLoader.
*/
internal val ProtocolTModLoader = protocol("tModLoader") {
// https://github.com/tModLoader/tModLoader/blob/1.4.4/patches/tModLoader/Terraria/NetMessage.cs.patch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ internal class ServerPlayConnectionHandler(

override fun initialize() {
player.previousServer = serverConnection.server.infoWithLastKnownVersion()
player.previousModsPacket = serverConnection.syncModsPacket
}

override fun disconnect() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
package org.lanternpowered.terre.impl.network.client

import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.util.concurrent.ScheduledFuture
import org.lanternpowered.terre.ProtocolVersion
import org.lanternpowered.terre.Team
import org.lanternpowered.terre.event.player.PlayerChangePvPEnabledEvent
import org.lanternpowered.terre.event.player.PlayerChangeTeamEvent
import org.lanternpowered.terre.event.player.PlayerRespawnEvent
import org.lanternpowered.terre.impl.Terre
import org.lanternpowered.terre.impl.addon.Mod
import org.lanternpowered.terre.impl.addon.TerreAddon
import org.lanternpowered.terre.impl.command.CommandManagerImpl
import org.lanternpowered.terre.impl.event.TerreEventBus
import org.lanternpowered.terre.impl.network.ConnectionHandler
import org.lanternpowered.terre.impl.network.Packet
import org.lanternpowered.terre.impl.network.buffer.PlayerId
import org.lanternpowered.terre.impl.network.buffer.writeString
import org.lanternpowered.terre.impl.network.packet.ClientUniqueIdPacket
import org.lanternpowered.terre.impl.network.packet.ConnectionApprovedPacket
import org.lanternpowered.terre.impl.network.packet.ItemRemoveOwnerPacket
Expand Down Expand Up @@ -51,6 +54,7 @@ import org.lanternpowered.terre.impl.player.PlayerImpl
import org.lanternpowered.terre.text.textOf
import java.time.Duration
import java.util.concurrent.TimeUnit
import kotlin.math.min

internal class ClientPlayConnectionHandler(
private val player: PlayerImpl
Expand Down Expand Up @@ -123,6 +127,35 @@ internal class ClientPlayConnectionHandler(
return false // Forward
}

override fun handle(packet: ModFileRequestPacket): Boolean {
val serverConnection = player.serverConnection
if (serverConnection != null && serverConnection.terreAddonSynced && packet.name == TerreAddon.mod.name) {
sendMod(TerreAddon.mod)
return true
}
return false // Forward
}

private fun sendMod(mod: Mod) {
// https://github.com/tModLoader/tModLoader/blob/d57cf0a6886e880bb1f6ec6c39b56ebe918d55c5/patches/tModLoader/Terraria/ModLoader/ModNet.cs#L409
val connection = player.clientConnection
val data = mod.data

var buf = Unpooled.buffer()
buf.writeString(mod.name)
buf.writeLongLE(data.size.toLong())
connection.send(ModFileResponsePacket(buf))

val chunkSize = 16384
var offset = 0
while (offset < data.size) {
val size = min(data.size - offset, chunkSize)
buf = Unpooled.wrappedBuffer(data, offset, size)
connection.send(ModFileResponsePacket(buf))
offset += size
}
}

override fun handle(packet: SyncModsDonePacket): Boolean {
val serverConnection = player.serverConnection
if (serverConnection != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/
package org.lanternpowered.terre.impl.network.packet.tmodloader

import com.google.common.io.BaseEncoding
import org.lanternpowered.terre.impl.network.Packet
import org.lanternpowered.terre.impl.network.buffer.readString
import org.lanternpowered.terre.impl.network.buffer.writeString
Expand All @@ -20,10 +19,10 @@ internal data class SyncModsPacket(
val mods: List<Mod>
) : Packet {

data class Mod(
class Mod(
val name: String,
val version: String,
val fileHash: String,
val hash: ByteArray,
val configs: List<ModConfig>
)
}
Expand All @@ -34,7 +33,7 @@ internal val SyncModsEncoder = PacketEncoder<SyncModsPacket> { buf, packet ->
for (mod in mods) {
buf.writeString(mod.name)
buf.writeString(mod.version)
buf.writeBytes(BaseEncoding.base16().decode(mod.fileHash))
buf.writeBytes(mod.hash)
val configs = mod.configs
buf.writeIntLE(configs.size)
for (config in configs) {
Expand All @@ -50,17 +49,16 @@ internal val SyncModsDecoder = PacketDecoder { buf ->
repeat(modCount) {
val name = buf.readString()
val version = buf.readString()
val fileHashBytes = ByteArray(20)
buf.readBytes(fileHashBytes)
val fileHash = BaseEncoding.base16().encode(fileHashBytes)
val hash = ByteArray(20)
buf.readBytes(hash)
val configCount = buf.readIntLE()
val configs = ArrayList<ModConfig>(configCount)
repeat(configCount) {
val configName = buf.readString()
val content = buf.readString()
configs += ModConfig(configName, content)
}
mods += SyncModsPacket.Mod(name, version, fileHash, configs)
mods += SyncModsPacket.Mod(name, version, hash, configs)
}
SyncModsPacket(mods)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.lanternpowered.terre.ServerConnectionRequestResult
import org.lanternpowered.terre.impl.ProxyImpl
import org.lanternpowered.terre.impl.ServerImpl
import org.lanternpowered.terre.impl.Terre
import org.lanternpowered.terre.impl.addon.TerreAddon
import org.lanternpowered.terre.impl.network.Connection
import org.lanternpowered.terre.impl.network.PacketCodecContextImpl
import org.lanternpowered.terre.impl.network.PacketDirection
Expand Down Expand Up @@ -48,8 +49,9 @@ internal class ServerConnectionImpl(
) : ServerConnection {

private var nullablePlayerId: PlayerId? = null
var syncModsPacket: SyncModsPacket? = null
private var syncModsPacket: SyncModsPacket? = null
var syncModNetIdsPacket: ModDataPacket? = null
var terreAddonSynced = false

/**
* The id that the player got assigned by the server.
Expand Down Expand Up @@ -81,7 +83,15 @@ internal class ServerConnectionImpl(
// Continue server connection after it has been approved
connection.setConnectionHandler(ServerPlayConnectionHandler(this@ServerConnectionImpl, player))

var modsPacket = syncModsPacket
val mods = ArrayList(syncModsPacket?.mods ?: listOf())
val addon = TerreAddon.mod
if (ProxyImpl.tModLoaderSyncAddonMod && mods.none { mod -> mod.name == addon.name }) {
mods.add(SyncModsPacket.Mod(addon.name, addon.version, addon.hash, listOf()))
terreAddonSynced = true
}
syncModsPacket = SyncModsPacket(mods.toList())
player.previousModsPacket = syncModsPacket

val tModLoaderClient = player.protocolVersion is ProtocolVersion.TModLoader
var syncMods = false
val syncConfig = ArrayList<UpdateModConfigResponsePacket.Success>()
Expand All @@ -100,14 +110,13 @@ internal class ServerConnectionImpl(
// Compare the mods between the current and the previous server, to check if they need to
// be updated
val previousMods = ArrayList(previousModsPacket?.mods ?: listOf())
val mods = ArrayList(modsPacket?.mods ?: listOf())
val modsIterator = mods.iterator()
while (modsIterator.hasNext()) {
val mod = modsIterator.next()
val previousMod = previousMods.find { previous ->
previous.name == mod.name &&
previous.version == mod.version &&
previous.fileHash == mod.fileHash
previous.hash.contentEquals(mod.hash)
}
if (previousMod != null) {
previousMods.remove(previousMod)
Expand All @@ -134,10 +143,7 @@ internal class ServerConnectionImpl(
}
}
if (syncMods) {
if (modsPacket == null) {
modsPacket = SyncModsPacket(listOf())
}
player.clientConnection.send(modsPacket)
player.clientConnection.send(SyncModsPacket(mods))
} else {
player.clientConnection.send(syncConfig)
// Sending this packet triggers the client to request all the information from the
Expand Down Expand Up @@ -175,7 +181,7 @@ internal class ServerConnectionImpl(
val protocolVersion = if (lastKnownVersion is ProtocolVersion.TModLoader) lastKnownVersion else player.protocolVersion
tModLoader = listOf(VersionedProtocol(protocolVersion, ProtocolTModLoader))
}
(tModLoader + ProtocolRegistry.allowedTranslations.asSequence()
(tModLoader + ProtocolRegistry.allowedTranslations(ProxyImpl.allowModdedClientsOnVanillaServers).asSequence()
.filter { translation -> translation.from == clientProtocol }
.flatMap { translation ->
ProtocolRegistry.all.asSequence().filter { it.protocol == translation.to }
Expand Down
Binary file added proxy/src/main/resources/TerreAddon.tmod
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Terre
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) contributors
*
* This work is licensed under the terms of the MIT License (MIT). For
* a copy, see 'LICENSE.txt' or <https://opensource.org/licenses/MIT>.
*/
package org.lanternpowered.terre.impl.util

import org.lanternpowered.terre.impl.addon.TerreAddon
import kotlin.test.Test
import kotlin.test.assertEquals

class ModLoaderTest {

@Test fun test() {
assertEquals("TerreAddon", TerreAddon.mod.name)
}
}
2 changes: 1 addition & 1 deletion test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ plugins {
dependencies {
implementation(project(":terre-proxy"))
implementation(project(":terre-portals"))
implementation(project(":terre-characters"))
// implementation(project(":terre-characters"))
// implementation(project(":terre-tshock-users"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

16 changes: 16 additions & 0 deletions tmodloader/TerreAddon/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"profiles": {
"Terraria": {
"commandName": "Executable",
"executablePath": "$(DotNetName)",
"commandLineArgs": "$(tMLPath)",
"workingDirectory": "$(tMLSteamPath)"
},
"TerrariaServer": {
"commandName": "Executable",
"executablePath": "$(DotNetName)",
"commandLineArgs": "$(tMLServerPath)",
"workingDirectory": "$(tMLSteamPath)"
}
}
}
Loading

0 comments on commit 04cf263

Please sign in to comment.