Browse Source

Implement world list packet

Signed-off-by: Graham <gpe@openrs2.org>
pull/132/head
Graham 4 months ago
parent
commit
8ff91b5ee2
  1. 10
      conf/src/main/kotlin/org/openrs2/conf/Config.kt
  2. 249
      conf/src/main/kotlin/org/openrs2/conf/CountryCode.kt
  3. 27
      etc/config.example.yaml
  4. 5
      game/src/main/kotlin/org/openrs2/game/GameModule.kt
  5. 8
      game/src/main/kotlin/org/openrs2/game/cluster/Cluster.kt
  6. 24
      game/src/main/kotlin/org/openrs2/game/cluster/CountryList.kt
  7. 30
      game/src/main/kotlin/org/openrs2/game/cluster/SingleWorldCluster.kt
  8. 22
      game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt
  9. 4
      protocol/src/main/kotlin/org/openrs2/protocol/Protocol.kt
  10. 39
      protocol/src/main/kotlin/org/openrs2/protocol/world/WorldListResponse.kt
  11. 137
      protocol/src/main/kotlin/org/openrs2/protocol/world/WorldListResponseCodec.kt

10
conf/src/main/kotlin/org/openrs2/conf/Config.kt

@ -3,7 +3,15 @@ package org.openrs2.conf
public data class Config(
val game: String,
val operator: String,
val domain: String
val domain: String,
val world: Int,
val hostname: String,
val country: CountryCode,
val activity: String,
val members: Boolean,
val quickChat: Boolean,
val pvp: Boolean,
val lootShare: Boolean
) {
val internalGame: String = game.toInternalName()
val internalOperator: String = operator.toInternalName()

249
conf/src/main/kotlin/org/openrs2/conf/CountryCode.kt

@ -0,0 +1,249 @@
package org.openrs2.conf
public enum class CountryCode {
/*
* The order of the country codes in this enum MUST match enum 1626 in the
* cache.
*/
AF, // Afghanistan
AL, // Albania
DZ, // Algeria
AS, // American Samoa
AD, // Andorra
AO, // Angola
AI, // Anguilla
AQ, // Antarctica
AG, // Antigua and Barbuda
AR, // Argentina
AM, // Armenia
AW, // Aruba
AU, // Australia
AT, // Austria
AZ, // Azerbaijan
BS, // Bahamas
BH, // Bahrain
BD, // Bangladesh
BB, // Barbados
BY, // Belarus
BE, // Belgium
BZ, // Belize
BJ, // Benin
BM, // Bermuda
BT, // Bhutan
BO, // Bolivia
BA, // Bosnia and Herzegovina
BW, // Botswana
BV, // Bouvet Island
BR, // Brazil
IO, // British Indian Ocean Territory
BN, // Brunei Darussalam
BG, // Bulgaria
BF, // Burkina Faso
BI, // Burundi
KH, // Cambodia
CM, // Cameroon
CA, // Canada
CV, // Cape Verde
KY, // Cayman Islands
CF, // Central African Republic
TD, // Chad
CL, // Chile
CN, // China
CX, // Christmas Island
CC, // Cocos (Keeling) Islands
CO, // Colombia
KM, // Comoros
CG, // Congo
CD, // Congo, The Democratic Republic of the
CK, // Cook Islands
CR, // Costa Rica
CI, // Cote D'Ivoire
HR, // Croatia
CU, // Cuba
CY, // Cyprus
CZ, // Czech Republic
DK, // Denmark
DJ, // Djibouti
DM, // Dominica
DO, // Dominican Republic
TL, // East Timor
EC, // Ecuador
EG, // Egypt
SV, // El Salvador
GQ, // Equatorial Guinea
ER, // Eritrea
EE, // Estonia
ET, // Ethiopia
FK, // Falkland Islands (Malvinas)
FO, // Faroe Islands
FJ, // Fiji
FI, // Finland
FR, // France
FX, // France, Metropolitan
GF, // French Guiana
PF, // French Polynesia
TF, // French Southern Territories
GA, // Gabon
GM, // Gambia
GE, // Georgia
DE, // Germany
GH, // Ghana
GI, // Gibraltar
GR, // Greece
GL, // Greenland
GD, // Grenada
GP, // Guadeloupe
GU, // Guam
GT, // Guatemala
GN, // Guinea
GW, // Guinea-Bissau
GY, // Guyana
HT, // Haiti
HM, // Heard Island and McDonald Islands
VA, // Holy See (Vatican City State)
HN, // Honduras
HK, // Hong Kong
HU, // Hungary
IS, // Iceland
IN, // India
ID, // Indonesia
IR, // Iran, Islamic Republic of
IQ, // Iraq
IE, // Ireland
IL, // Israel
IT, // Italy
JM, // Jamaica
JP, // Japan
JO, // Jordan
KZ, // Kazakstan
KE, // Kenya
KI, // Kiribati
KP, // Korea, Democratic People's Republic of
KR, // Korea, Republic of
KW, // Kuwait
KG, // Kyrgyzstan
LA, // Lao People's Democratic Republic
LV, // Latvia
LB, // Lebanon
LS, // Lesotho
LR, // Liberia
LY, // Libyan Arab Jamahiriya
LI, // Liechtenstein
LT, // Lithuania
LU, // Luxembourg
MO, // Macau
MK, // Macedonia, the Former Yugoslav Republic of
MG, // Madagascar
MW, // Malawi
MY, // Malaysia
MV, // Maldives
ML, // Mali
MT, // Malta
MH, // Marshall Islands
MQ, // Martinique
MR, // Mauritania
MU, // Mauritius
YT, // Mayotte
MX, // Mexico
FM, // Micronesia, Federated States of
MD, // Moldova, Republic of
MC, // Monaco
MN, // Mongolia
ME, // Montenegro
MS, // Montserrat
MA, // Morocco
MZ, // Mozambique
MM, // Myanmar
NA, // Namibia
NR, // Nauru
NP, // Nepal
NL, // Netherlands
AN, // Netherlands Antilles
NC, // New Caledonia
NZ, // New Zealand
NI, // Nicaragua
NE, // Niger
NG, // Nigeria
NU, // Niue
NF, // Norfolk Island
MP, // Northern Mariana Islands
NO, // Norway
OM, // Oman
PK, // Pakistan
PW, // Palau
PS, // Palestinian Territory, Occupied
PA, // Panama
PG, // Papua New Guinea
PY, // Paraguay
PE, // Peru
PH, // Philippines
PN, // Pitcairn
PL, // Poland
PT, // Portugal
PR, // Puerto Rico
QA, // Qatar
RE, // Reunion
RO, // Romania
RU, // Russian Federation
RW, // Rwanda
SH, // Saint Helena
KN, // Saint Kitts and Nevis
LC, // Saint Lucia
PM, // Saint Pierre and Miquelon
VC, // Saint Vincent and the Grenadines
WS, // Samoa
SM, // San Marino
ST, // Sao Tome and Principe
SA, // Saudi Arabia
SN, // Senegal
RS, // Serbia
SC, // Seychelles
SL, // Sierra Leone
SG, // Singapore
SK, // Slovakia
SI, // Slovenia
SB, // Solomon Islands
SO, // Somalia
ZA, // South Africa
GS, // South Georgia and the South Sandwich Islands
ES, // Spain
LK, // Sri Lanka
SD, // Sudan
SR, // Suriname
SJ, // Svalbard and Jan Mayen
SZ, // Swaziland
SE, // Sweden
CH, // Switzerland
SY, // Syrian Arab Republic
TW, // Taiwan
TJ, // Tajikistan
TZ, // Tanzania, United Republic of
TH, // Thailand
TG, // Togo
TK, // Tokelau
TO, // Tonga
TT, // Trinidad and Tobago
TN, // Tunisia
TR, // Turkey
TM, // Turkmenistan
TC, // Turks and Caicos Islands
TV, // Tuvalu
UG, // Uganda
UA, // Ukraine
AE, // United Arab Emirates
GB, // United Kingdom
US, // United States
UM, // United States Minor Outlying Islands
UY, // Uruguay
UZ, // Uzbekistan
VU, // Vanuatu
VE, // Venezuela
VN, // Vietnam
VG, // Virgin Islands, British
VI, // Virgin Islands, U.S.
WF, // Wallis and Futuna
EH, // Western Sahara
YE, // Yemen
ZM, // Zambia
ZW, // Zimbabwe
}

27
etc/config.example.yaml

@ -10,3 +10,30 @@ operator: "OpenRS2"
# The game's domain name. All references to "runescape.com" are replaced with
# this string. It should, at a minimum, have `www` and `www-wtqa` hostnames.
domain: "openrs2.org"
# The world number.
world: 1
# The world server's hostname. A suggested naming scheme is
# `world<world>.<domain>` - for example, `world1.openrs2.org`. The default of
# `localhost` is only suitable for local testing.
hostname: "localhost"
# The world's ISO 3166-1 alpha-2 country code.
country: "GB"
# The world's activity. If the world does not have a special purpose, use a
# single hyphen.
activity: "-"
# Set to true to indicate this is a members world.
members: true
# Set to true to enforce the use of quick chat.
quick_chat: false
# Set to true to indicate this is a PvP world.
pvp: false
# Set to true to enable loot share.
loot_share: false

5
game/src/main/kotlin/org/openrs2/game/GameModule.kt

@ -13,6 +13,8 @@ import org.openrs2.conf.ConfigModule
import org.openrs2.game.cache.CacheProvider
import org.openrs2.game.cache.Js5MasterIndexProvider
import org.openrs2.game.cache.StoreProvider
import org.openrs2.game.cluster.Cluster
import org.openrs2.game.cluster.SingleWorldCluster
import org.openrs2.game.net.NetworkService
import org.openrs2.game.net.js5.Js5Service
import org.openrs2.net.NetworkModule
@ -40,5 +42,8 @@ public object GameModule : AbstractModule() {
bind(Cache::class.java)
.toProvider(CacheProvider::class.java)
.`in`(Scopes.SINGLETON)
bind(Cluster::class.java)
.to(SingleWorldCluster::class.java)
}
}

8
game/src/main/kotlin/org/openrs2/game/cluster/Cluster.kt

@ -0,0 +1,8 @@
package org.openrs2.game.cluster
import org.openrs2.protocol.world.WorldListResponse
import java.util.SortedMap
public interface Cluster {
public fun getWorldList(): Pair<SortedMap<Int, WorldListResponse.World>, SortedMap<Int, Int>>
}

24
game/src/main/kotlin/org/openrs2/game/cluster/CountryList.kt

@ -0,0 +1,24 @@
package org.openrs2.game.cluster
import org.openrs2.cache.config.enum.EnumTypeList
import org.openrs2.conf.CountryCode
import org.openrs2.protocol.world.WorldListResponse
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class CountryList @Inject constructor(
enums: EnumTypeList
) {
private val ids = enums[COUNTRY_IDS]!!
private val names = enums[COUNTRY_NAMES]!!
public operator fun get(code: CountryCode): WorldListResponse.Country {
return WorldListResponse.Country(ids.getInt(code.ordinal), names.getString(code.ordinal))
}
private companion object {
private const val COUNTRY_IDS = 1669
private const val COUNTRY_NAMES = 1626
}
}

30
game/src/main/kotlin/org/openrs2/game/cluster/SingleWorldCluster.kt

@ -0,0 +1,30 @@
package org.openrs2.game.cluster
import org.openrs2.conf.Config
import org.openrs2.protocol.world.WorldListResponse
import java.util.SortedMap
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class SingleWorldCluster @Inject constructor(
config: Config,
countries: CountryList
) : Cluster {
private val worlds = sortedMapOf(
config.world to WorldListResponse.World(
countries[config.country],
config.members,
config.quickChat,
config.pvp,
config.lootShare,
config.activity,
config.hostname
)
)
private val players = sortedMapOf(0 to 0)
override fun getWorldList(): Pair<SortedMap<Int, WorldListResponse.World>, SortedMap<Int, Int>> {
return Pair(worlds, players)
}
}

22
game/src/main/kotlin/org/openrs2/game/net/login/LoginChannelHandler.kt

@ -11,10 +11,12 @@ import io.netty.handler.codec.http.HttpResponseEncoder
import io.netty.handler.codec.string.StringDecoder
import io.netty.handler.timeout.IdleStateEvent
import org.openrs2.buffer.copiedBuffer
import org.openrs2.game.cluster.Cluster
import org.openrs2.game.net.crossdomain.CrossDomainChannelHandler
import org.openrs2.game.net.http.Http
import org.openrs2.game.net.jaggrab.JaggrabChannelHandler
import org.openrs2.game.net.js5.Js5ChannelHandler
import org.openrs2.protocol.Protocol
import org.openrs2.protocol.Rs2Decoder
import org.openrs2.protocol.Rs2Encoder
import org.openrs2.protocol.jaggrab.JaggrabRequestDecoder
@ -23,10 +25,12 @@ import org.openrs2.protocol.js5.Js5ResponseEncoder
import org.openrs2.protocol.js5.XorDecoder
import org.openrs2.protocol.login.LoginRequest
import org.openrs2.protocol.login.LoginResponse
import org.openrs2.protocol.world.WorldListResponse
import javax.inject.Inject
import javax.inject.Provider
public class LoginChannelHandler @Inject constructor(
private val cluster: Cluster,
private val js5HandlerProvider: Provider<Js5ChannelHandler>,
private val jaggrabHandler: JaggrabChannelHandler
) : SimpleChannelInboundHandler<LoginRequest>(LoginRequest::class.java) {
@ -38,6 +42,7 @@ public class LoginChannelHandler @Inject constructor(
when (msg) {
is LoginRequest.InitJs5RemoteConnection -> handleInitJs5RemoteConnection(ctx, msg)
is LoginRequest.InitJaggrabConnection -> handleInitJaggrabConnection(ctx)
is LoginRequest.RequestWorldList -> handleRequestWorldList(ctx, msg)
is LoginRequest.InitCrossDomainConnection -> handleInitCrossDomainConnection(ctx)
}
}
@ -74,6 +79,23 @@ public class LoginChannelHandler @Inject constructor(
ctx.pipeline().remove(this)
}
private fun handleRequestWorldList(ctx: ChannelHandlerContext, msg: LoginRequest.RequestWorldList) {
val (worlds, players) = cluster.getWorldList()
val checksum = worlds.hashCode()
val worldList = if (checksum != msg.checksum) {
WorldListResponse.WorldList(worlds, checksum)
} else {
null
}
val encoder = ctx.pipeline().get(Rs2Encoder::class.java)
encoder.protocol = Protocol.WORLD_LIST_DOWNSTREAM
ctx.write(WorldListResponse(worldList, players)).addListener(ChannelFutureListener.CLOSE)
}
private fun handleInitCrossDomainConnection(ctx: ChannelHandlerContext) {
ctx.pipeline().addLast(
HttpRequestDecoder(),

4
protocol/src/main/kotlin/org/openrs2/protocol/Protocol.kt

@ -8,6 +8,7 @@ import org.openrs2.protocol.login.IpLimitCodec
import org.openrs2.protocol.login.Js5OkCodec
import org.openrs2.protocol.login.RequestWorldListCodec
import org.openrs2.protocol.login.ServerFullCodec
import org.openrs2.protocol.world.WorldListResponseCodec
public class Protocol(vararg codecs: PacketCodec<*>) {
private val decoders = arrayOfNulls<PacketCodec<*>>(256)
@ -42,5 +43,8 @@ public class Protocol(vararg codecs: PacketCodec<*>) {
ServerFullCodec,
IpLimitCodec
)
public val WORLD_LIST_DOWNSTREAM: Protocol = Protocol(
WorldListResponseCodec
)
}
}

39
protocol/src/main/kotlin/org/openrs2/protocol/world/WorldListResponse.kt

@ -0,0 +1,39 @@
package org.openrs2.protocol.world
import org.openrs2.protocol.Packet
import java.util.SortedMap
public data class WorldListResponse(
public val worldList: WorldList?,
public val players: SortedMap<Int, Int>
) : Packet {
public data class Country(
public val id: Int,
public val name: String
) : Comparable<Country> {
override fun compareTo(other: Country): Int {
return compareValuesBy(this, other, Country::name, Country::id)
}
}
public data class World(
public val country: Country,
public val members: Boolean,
public val quickChat: Boolean,
public val pvp: Boolean,
public val lootShare: Boolean,
public val activity: String,
public val hostname: String
)
public data class WorldList(
public val worlds: SortedMap<Int, World>,
public val checksum: Int
) {
public val countries: List<Country> = worlds.values.map(World::country).distinct().sorted()
}
public companion object {
public const val OFFLINE: Int = -1
}
}

137
protocol/src/main/kotlin/org/openrs2/protocol/world/WorldListResponseCodec.kt

@ -0,0 +1,137 @@
package org.openrs2.protocol.world
import io.netty.buffer.ByteBuf
import org.openrs2.buffer.readUnsignedShortSmart
import org.openrs2.buffer.readVersionedString
import org.openrs2.buffer.writeUnsignedShortSmart
import org.openrs2.buffer.writeVersionedString
import org.openrs2.crypto.StreamCipher
import org.openrs2.protocol.PacketCodec
import org.openrs2.protocol.PacketLength
public object WorldListResponseCodec : PacketCodec<WorldListResponse>(
type = WorldListResponse::class.java,
opcode = 0,
length = PacketLength.VARIABLE_SHORT
) {
private const val VERSION = 1
private const val FLAG_MEMBERS_ONLY = 0x1
private const val FLAG_QUICK_CHAT = 0x2
private const val FLAG_PVP = 0x4
private const val FLAG_LOOT_SHARE = 0x8
override fun decode(input: ByteBuf, cipher: StreamCipher): WorldListResponse {
val version = input.readUnsignedByte().toInt()
require(version == VERSION) {
"Unsupported world list version"
}
val cluster = if (input.readBoolean()) {
var size = input.readUnsignedShortSmart()
val countries = mutableListOf<WorldListResponse.Country>()
for (i in 0 until size) {
val id = input.readUnsignedShortSmart()
val name = input.readVersionedString()
countries += WorldListResponse.Country(id, name)
}
val firstId = input.readUnsignedShortSmart()
input.readUnsignedShortSmart()
size = input.readUnsignedShortSmart()
val worlds = sortedMapOf<Int, WorldListResponse.World>()
for (i in 0 until size) {
val id = firstId + input.readUnsignedShortSmart()
val countryIndex = input.readUnsignedByte().toInt()
require(countryIndex >= countries.size) {
"Country index out of bounds"
}
val country = countries[countryIndex]
val flags = input.readInt()
val members = (flags and FLAG_MEMBERS_ONLY) != 0
val quickChat = (flags and FLAG_QUICK_CHAT) != 0
val pvp = (flags and FLAG_PVP) != 0
val lootShare = (flags and FLAG_LOOT_SHARE) != 0
val activity = input.readVersionedString()
val hostname = input.readVersionedString()
worlds[id] = WorldListResponse.World(country, members, quickChat, pvp, lootShare, activity, hostname)
}
val checksum = input.readInt()
WorldListResponse.WorldList(worlds, checksum)
} else {
null
}
val players = sortedMapOf<Int, Int>()
while (input.isReadable) {
val offset = input.readUnsignedShortSmart()
val count = input.readUnsignedShort()
players[offset] = count
}
return WorldListResponse(cluster, players)
}
override fun encode(input: WorldListResponse, output: ByteBuf, cipher: StreamCipher) {
output.writeByte(VERSION)
val worlds = input.worldList
if (worlds != null) {
output.writeBoolean(true)
output.writeUnsignedShortSmart(worlds.countries.size)
for (country in worlds.countries) {
output.writeUnsignedShortSmart(country.id)
output.writeVersionedString(country.name)
}
val firstId = worlds.worlds.firstKey() ?: 0
val lastId = worlds.worlds.lastKey() ?: 0
output.writeUnsignedShortSmart(firstId)
output.writeUnsignedShortSmart(lastId)
output.writeUnsignedShortSmart(worlds.worlds.size)
for ((id, world) in worlds.worlds) {
output.writeUnsignedShortSmart(id - firstId)
output.writeByte(worlds.countries.binarySearch(world.country))
var flags = 0
if (world.members) {
flags = flags or FLAG_MEMBERS_ONLY
}
if (world.quickChat) {
flags = flags or FLAG_QUICK_CHAT
}
if (world.pvp) {
flags = flags or FLAG_PVP
}
if (world.lootShare) {
flags = flags or FLAG_LOOT_SHARE
}
output.writeInt(flags)
output.writeVersionedString(world.activity)
output.writeVersionedString(world.hostname)
}
output.writeInt(worlds.checksum)
} else {
output.writeBoolean(false)
}
for ((offset, count) in input.players) {
output.writeUnsignedShortSmart(offset)
output.writeShort(count)
}
}
}
Loading…
Cancel
Save