Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public interface Rs3PacketDefinitions : Rs3AppearanceDefinitions {

public fun getQuickChatPhrase(id: Int): Rs3QuickChatPhrase

public fun getGameLogEvent(id: Int): Rs3GameLogEventDefinition = error("RS3 game-log definitions are unavailable")

public fun getVariable(
domain: Rs3VariableDomain,
id: Int,
Expand Down Expand Up @@ -63,6 +65,10 @@ public data class Rs3QuickChatPhrase(
public val commands: List<Rs3QuickChatCommand>,
)

public data class Rs3GameLogEventDefinition(
public val parameters: List<Rs3VariableDefinition>,
)

public data class Rs3QuickChatCommand(
public val id: Int,
public val transmitBytes: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ internal class Rs3Js5Connection(
val batch = ByteBuffer.allocate(MAX_IN_FLIGHT * 10)
while (queued.hasNext() && pending.size < MAX_IN_FLIGHT) {
val request = queued.next()
batch.put(1).put(request.archive.toByte()).putInt(request.group).putInt(0)
batch
.put(1)
.put(request.archive.toByte())
.putInt(request.group)
.putInt(0)
pending[request] = Response()
}
if (batch.position() != 0) {
Expand Down
23 changes: 18 additions & 5 deletions cache/src/main/kotlin/net/rsprox/cache/rs3/Rs3LiveCacheResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.michaelbull.logging.InlineLogger
import io.netty.buffer.Unpooled
import net.rsprox.cache.CACHES_DIRECTORY
import net.rsprox.cache.api.rs3.Rs3AppearanceItem
import net.rsprox.cache.api.rs3.Rs3GameLogEventDefinition
import net.rsprox.cache.api.rs3.Rs3NpcDefinition
import net.rsprox.cache.api.rs3.Rs3PacketDefinitions
import net.rsprox.cache.api.rs3.Rs3QuickChatPhrase
Expand Down Expand Up @@ -33,17 +34,19 @@ public class Rs3LiveCacheResolver(
public var masterIndexSnapshot: ByteArray = ByteArray(0)
private set

public fun loadPacketDefinitions(
onProgress: (String, Int, Int) -> Unit = { _, _, _ -> },
): Rs3PacketDefinitions {
public fun loadPacketDefinitions(onProgress: (String, Int, Int) -> Unit = { _, _, _ -> }): Rs3PacketDefinitions {
require(info.revision == 950) { "RS3 definition decoding is currently verified for revision 950 only" }
Rs3Js5Connection(info).use { connection ->
onProgress("Loading cache indexes", 0, 0)
val master = unpack(connection.get(255, 255))
var groups = 0
var downloads = 0
val prefetch: (Int, List<Js5Index.MutableGroup>) -> Unit = { archive, entries ->
val missing = entries.filter { readCachedGroup(directory, archive, it.id, it.version, it.checksum) == null }
val missing =
entries.filter {
readCachedGroup(directory, archive, it.id, it.version, it.checksum) ==
null
}
val byId = missing.associateBy { it.id }
connection.getAll(missing.map { Rs3Js5Connection.Request(archive, it.id) }) { request, bytes ->
val group = byId.getValue(request.group)
Expand Down Expand Up @@ -211,6 +214,7 @@ public class Rs3LiveCacheResolver(
.mapValues { (_, bytes) -> Rs3PacketDefinitionDecoder.phrase(bytes) }
onProgress("Loading variable definitions", 0, 0)
val configIndex = index(2)
val gameLogEvents = configIndex[70]?.let { files(2, it) }.orEmpty()
val varbits =
files(2, checkNotNull(configIndex[69]) { "Missing varbit definitions" })
.mapValues { (_, bytes) -> Rs3PacketDefinitionDecoder.varbit(bytes) }
Expand Down Expand Up @@ -239,7 +243,7 @@ public class Rs3LiveCacheResolver(
"${npcs.size} NPCs, ${phrases.size} phrases, " +
"${(System.nanoTime() - started) / 1_000_000} ms"
}
return Snapshot(slots, items, phrases, variables, npcs, varbits)
return Snapshot(slots, items, phrases, variables, npcs, varbits, gameLogEvents)
}

private class Snapshot(
Expand All @@ -249,8 +253,17 @@ public class Rs3LiveCacheResolver(
private val variables: Map<Rs3VariableDomain, Map<Int, Rs3VariableDefinition>>,
private val npcs: Map<Int, ByteArray>,
private val varbits: Map<Int, Rs3VarbitDefinition>,
private val gameLogEvents: Map<Int, ByteArray>,
) : Rs3PacketDefinitions {
private val decodedNpcs = ConcurrentHashMap<Int, Rs3NpcDefinition>()
private val decodedGameLogEvents = ConcurrentHashMap<Int, Rs3GameLogEventDefinition>()

override fun getGameLogEvent(id: Int): Rs3GameLogEventDefinition =
decodedGameLogEvents.computeIfAbsent(id) {
Rs3PacketDefinitionDecoder.gameLogEvent(
checkNotNull(gameLogEvents[id]) { "Missing RS3 game-log event definition $id" },
)
}

override fun getVarbit(id: Int): Rs3VarbitDefinition =
checkNotNull(varbits[id]) { "Missing RS3 varbit definition $id" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.rsprox.cache.rs3

import net.rsprox.cache.api.rs3.Rs3BaseVarType
import net.rsprox.cache.api.rs3.Rs3GameLogEventDefinition
import net.rsprox.cache.api.rs3.Rs3QuickChatCommand
import net.rsprox.cache.api.rs3.Rs3QuickChatPhrase
import net.rsprox.cache.api.rs3.Rs3VarbitDefinition
Expand All @@ -9,6 +10,27 @@ import java.nio.ByteBuffer

/** Revision-950 definition grammars; unsupported opcodes/types fail rather than guess a wire width. */
internal object Rs3PacketDefinitionDecoder {
// Note(revision): 950 beta, config 2:70, native 0x9ddaa0: repeated opcode 103 + gSmart1or2 type ID.
fun gameLogEvent(bytes: ByteArray): Rs3GameLogEventDefinition {
val input = ByteBuffer.wrap(bytes)
val parameters = mutableListOf<Rs3VariableDefinition>()
while (true) {
when (val opcode = input.byte()) {
0 -> {
require(!input.hasRemaining()) { "Trailing game-log definition bytes" }
return Rs3GameLogEventDefinition(parameters)
}
103 -> {
val id = if (input.get(input.position()) < 0) input.word() - 32768 else input.byte()
val type = baseType(id)
require(type != Rs3BaseVarType.COORDINATE) { "Unsupported game-log parameter type $id" }
parameters += Rs3VariableDefinition(id, type)
}
else -> error("Unsupported game-log definition opcode $opcode")
}
}
}

fun varbit(bytes: ByteArray): Rs3VarbitDefinition {
val input = ByteBuffer.wrap(bytes)
var domain = -1
Expand All @@ -32,6 +54,7 @@ internal object Rs3PacketDefinitionDecoder {
end = input.byte()
}
16 -> Unit
17 -> Unit // luaexcluded: no payload or effect on packet decoding.
else -> error("Unknown RS3 varbit opcode $opcode")
}
}
Expand All @@ -51,6 +74,7 @@ internal object Rs3PacketDefinitionDecoder {
4, 5 -> input.byte()
110 -> input.short
7, 8 -> Unit
9 -> Unit // luaexcluded: no payload or effect on packet decoding.
else -> error("Unsupported variable definition opcode $opcode")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import java.net.URL
import javax.swing.BoxLayout
import javax.swing.DefaultComboBoxModel
import javax.swing.DefaultListCellRenderer
import javax.swing.JCheckBox
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.JMenuItem
Expand Down Expand Up @@ -63,6 +64,13 @@ public class LaunchBar(
private val jagexAuthenticator = JagexAuthenticator()
private val sessionTypesModel = DefaultComboBoxModel(sessionTypes)
private val charactersModel = DefaultComboBoxModel<JagexCharacter>()
private val rs3BetaToggle =
JCheckBox("Beta").apply {
isOpaque = false
isSelected = App.service.getSelectedRs3Beta()
toolTipText = "Use the RuneScape 3 beta client and servers"
addActionListener { App.service.setSelectedRs3Beta(isSelected) }
}

public lateinit var clientTypeDropdown: FlatComboBox<SessionType>
private set
Expand Down Expand Up @@ -165,6 +173,7 @@ public class LaunchBar(

fun updateProxyTargetRowState() {
val isRs3 = (clientTypeDropdown.selectedItem as? SessionType)?.isRs3 == true
rs3BetaToggle.isVisible = isRs3
val disabledReason =
if (isRs3) "Proxy target selection is currently only supported for Old School RuneScape." else null
proxyTargetLabel.isEnabled = !isRs3
Expand All @@ -174,6 +183,8 @@ public class LaunchBar(
proxyTargetLabel.toolTipText = disabledReason
proxyTargetDropdown.toolTipText = disabledReason
importTargetsButton.toolTipText = disabledReason ?: "Import Proxy Targets"
revalidate()
repaint()
}

clientTypeDropdown.addActionListener {
Expand All @@ -192,6 +203,7 @@ public class LaunchBar(
add(createFieldLabel("Client Type"), "wrap")
add(characterDropdown, "growx, h $CONTROL_HEIGHT!")
add(clientTypeDropdown, "growx, h $CONTROL_HEIGHT!, wrap")
add(rs3BetaToggle, "spanx 2, hidemode 3, wrap")
add(proxyTargetLabel, "spanx 2, growx, gaptop 4, wrap")
add(proxyTargetDropdown, "growx, h $CONTROL_HEIGHT!, spanx 2, split 2")
add(importTargetsButton, "w $CONTROL_HEIGHT!, h $CONTROL_HEIGHT!, wrap")
Expand Down Expand Up @@ -231,7 +243,7 @@ public class LaunchBar(
DEFAULT_CHARACTER -> null
else -> character as JagexCharacter
}
sessionsPanel.createSession(sessionType, character)
sessionsPanel.createSession(sessionType, character, rs3Beta = sessionType.isRs3 && rs3BetaToggle.isSelected)
}

private fun importProxyTargets() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ internal class Rs3LaunchProgressPanel : JPanel(BorderLayout(0, 6)) {
}
}

fun finish(success: Boolean) {
fun finish(
success: Boolean,
failure: String? = null,
) {
SwingUtilities.invokeLater {
if (finished) return@invokeLater
finished = true
Expand All @@ -46,6 +49,7 @@ internal class Rs3LaunchProgressPanel : JPanel(BorderLayout(0, 6)) {
} else {
bar.isVisible = false
status.text = "Launch failed during: ${progress.stage}. See the application log for details."
status.toolTipText = failure
}
revalidate()
repaint()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class SessionPanel(
public val type: SessionType,
private val sessionsPanel: SessionsPanel,
character: JagexCharacter?,
private val rs3Beta: Boolean = false,
) : JPanel() {
private val treeTable = JXTreeTable()
private val tableModel = DefaultTreeTableModel()
Expand Down Expand Up @@ -278,12 +279,19 @@ public class SessionPanel(
rs3SessionMonitor,
character,
upstreamJavConfigUrl =
if (type == SessionType.RS3_VULKAN) {
if (rs3Beta) {
if (type == SessionType.RS3_VULKAN) {
JagexNativeClientDownloader.BETA_VULKAN_RS3_JAV_CONFIG_URL
} else {
JagexNativeClientDownloader.BETA_RS3_JAV_CONFIG_URL
}
} else if (type == SessionType.RS3_VULKAN) {
JagexNativeClientDownloader.VULKAN_RS3_JAV_CONFIG_URL
} else {
JagexNativeClientDownloader.DEFAULT_RS3_JAV_CONFIG_URL
},
onProgress = { launchProgress?.update(it) },
beta = rs3Beta,
)
portNumber = handle.port
}
Expand All @@ -292,7 +300,7 @@ public class SessionPanel(
logger.error(e) {
"Unable to launch $type client"
}
launchProgress?.finish(success = false)
launchProgress?.finish(success = false, failure = e.message)
return@submit
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ public class SessionsPanel(
public fun createSession(
type: SessionType,
character: JagexCharacter?,
rs3Beta: Boolean = false,
) {
if (type.isRs3 && !rs3LaunchWarning.confirm(this)) return

val session = SessionPanel(type, this, character)
val session = SessionPanel(type, this, character, rs3Beta = type.isRs3 && rs3Beta)
addTab("Session ${++counter}", type.icon, session, "")
selectedComponent = session
setTabCloseCallback(tabCount - 1) { tabbedPane, tabIndex ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,30 @@ public data class NativePatchCriteria(
return this
}

/** Revision-950 Windows live lobby/world endpoint construction; never patch arbitrary HTTPS constants. */
/** Revision-950 Windows lobby/world endpoint construction; never patch arbitrary HTTPS constants. */
public fun rs3LoginPorts(
primary: Int,
alternate: Int,
beta: Boolean = false,
): Builder {
require(type == NativeClientType.RS3_WIN)
require(primary in 1024..65535 && alternate in 1024..65535 && primary != alternate)
require(primary != DEFAULT_PORT && alternate != DEFAULT_PORT)
val first = intToHexStringLE(primary)
val second = intToHexStringLE(alternate)
// Note: verified for revision 950 Windows OpenGL (binaryType=2) and Vulkan (binaryType=10).
val modeOffset = if (beta) "909A0100" else "709A0100"
// Note: live 950 OpenGL/Vulkan use 0x19A70; the verified beta-1 builds use 0x19A90.
// Beta callers must identify the supported executable before choosing these instruction windows.
// Each guarded instruction window must occur exactly once. Only the two mov immediates change.
// Later lobby selection, world selection, and the second world selection entry point respectively.
val sites =
listOf(
"483986709A0100750E41BE4AAA000041BCBB010000EB1AB8C0630000" to
"483986709A0100750E41BE${first}41BC${second}EB1AB8C0630000",
"493980709A0100750CBF4AAA0000BEBB010000EB18B8C0630000" to
"493980709A0100750CBF${first}BE${second}EB18B8C0630000",
"483982709A0100750E41BE4AAA000041BFBB010000EB1BB8C0630000" to
"483982709A0100750E41BE${first}41BF${second}EB1BB8C0630000",
"483986${modeOffset}750E41BE4AAA000041BCBB010000EB1AB8C0630000" to
"483986${modeOffset}750E41BE${first}41BC${second}EB1AB8C0630000",
"493980${modeOffset}750CBF4AAA0000BEBB010000EB18B8C0630000" to
"493980${modeOffset}750CBF${first}BE${second}EB18B8C0630000",
"483982${modeOffset}750E41BE4AAA000041BFBB010000EB1BB8C0630000" to
"483982${modeOffset}750E41BE${first}41BF${second}EB1BB8C0630000",
)
for ((old, new) in sites) {
wildcardByteSequence(
Expand Down
14 changes: 14 additions & 0 deletions protocol/rs3-950-beta/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
dependencies {
implementation(platform(rootProject.libs.netty.bom))
implementation(rootProject.libs.netty.buffer)
implementation(rootProject.libs.netty.transport)
implementation(rootProject.libs.netty.handler)
implementation(rootProject.libs.rsprot.buffer)
implementation(rootProject.libs.rsprot.compression)
implementation(rootProject.libs.rsprot.protocol)
implementation(rootProject.libs.rsprot.crypto)
implementation(platform(rootProject.libs.log4j.bom))
implementation(rootProject.libs.bundles.log4j)
implementation(projects.protocol)
implementation(projects.cache.cacheApi)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.rsprox.protocol.rs3v950beta

import net.rsprot.buffer.JagByteBuf
import net.rsprot.compression.HuffmanCodec
import net.rsprot.protocol.message.IncomingMessage
import net.rsprox.protocol.ClientPacketDecoder
import net.rsprox.protocol.rs3v950beta.game.incoming.decoder.prot.ClientMessageDecoderRepository
import net.rsprox.protocol.session.Session

public class ClientPacketDecoderServiceRs3V950Beta(
huffmanCodec: HuffmanCodec,
) : ClientPacketDecoder {
@OptIn(ExperimentalStdlibApi::class)
private val repository = ClientMessageDecoderRepository.build(huffmanCodec)

override fun decode(
opcode: Int,
payload: JagByteBuf,
session: Session,
): IncomingMessage {
val decoder = repository.getDecoder(opcode)
val message = decoder.decode(payload, session)
require(payload.readableBytes() == 0) {
"${decoder.prot} (opcode $opcode) left ${payload.readableBytes()} payload bytes unread"
}
return message
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.rsprox.protocol.rs3v950beta

import net.rsprox.protocol.ProtProvider
import net.rsprox.protocol.rs3v950beta.game.incoming.decoder.prot.GameClientProt
import kotlin.enums.EnumEntries

public data object GameClientProtProviderRs3V950Beta : ProtProvider<GameClientProt> {
override fun get(opcode: Int): GameClientProt {
return GameClientProt.entries.firstOrNull { it.opcode == opcode }
?: throw IllegalArgumentException("Unknown game client prot: $opcode")
}

override fun allProts(): EnumEntries<GameClientProt> {
return GameClientProt.entries
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.rsprox.protocol.rs3v950beta

import net.rsprox.protocol.ProtProvider
import net.rsprox.protocol.rs3v950beta.game.outgoing.decoder.prot.GameServerProt
import kotlin.enums.EnumEntries

public data object GameServerProtProviderRs3V950Beta : ProtProvider<GameServerProt> {
override fun get(opcode: Int): GameServerProt {
return GameServerProt.entries.firstOrNull { it.opcode == opcode }
?: throw IllegalArgumentException("Unknown game server prot: $opcode")
}

override fun allProts(): EnumEntries<GameServerProt> {
return GameServerProt.entries
}
}
Loading
Loading