Legacy ping support (#4062)

* Detection of protocol versions before 18w47b

* Refactor old_protocol_versions into protocol_version

* Ping servers closer to how a client of an instance's version would ping a server

* Allow pinging legacy servers from a modern profile in the same way a modern client would

* Ping 1.4.2 through 1.5.2 like a Vanilla client in those versions would when in such an instance
This commit is contained in:
Josiah Glosson 2025-07-28 07:44:34 -07:00 committed by GitHub
parent 13103b4950
commit 175b90be5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 705 additions and 45 deletions

44
Cargo.lock generated
View File

@ -5731,6 +5731,17 @@ dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_macros 0.12.1",
"phf_shared 0.12.1",
"serde",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
@ -5781,6 +5792,16 @@ dependencies = [
"rand 0.8.5",
]
[[package]]
name = "phf_generator"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
dependencies = [
"fastrand 2.3.0",
"phf_shared 0.12.1",
]
[[package]]
name = "phf_macros"
version = "0.10.0"
@ -5808,6 +5829,19 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "phf_macros"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d713258393a82f091ead52047ca779d37e5766226d009de21696c4e667044368"
dependencies = [
"phf_generator 0.12.1",
"phf_shared 0.12.1",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -5835,6 +5869,15 @@ dependencies = [
"siphasher 1.0.1",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher 1.0.1",
]
[[package]]
name = "pin-project"
version = "1.1.10"
@ -8946,6 +8989,7 @@ dependencies = [
"notify-debouncer-mini",
"p256",
"paste",
"phf 0.12.1",
"png",
"quartz_nbt",
"quick-xml 0.37.5",

View File

@ -99,6 +99,7 @@ notify = { version = "8.0.0", default-features = false }
notify-debouncer-mini = { version = "0.6.0", default-features = false }
p256 = "0.13.2"
paste = "1.0.15"
phf = { version = "0.12.1", features = ["macros"] }
png = "0.17.16"
prometheus = "0.14.0"
quartz_nbt = "0.2.9"

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import {
type ProtocolVersion,
type ServerWorld,
type ServerData,
type WorldWithProfile,
@ -33,7 +34,7 @@ const theme = useTheming()
const jumpBackInItems = ref<JumpBackInItem[]>([])
const serverData = ref<Record<string, ServerData>>({})
const protocolVersions = ref<Record<string, number | null>>({})
const protocolVersions = ref<Record<string, ProtocolVersion | null>>({})
const MIN_JUMP_BACK_IN = 3
const MAX_JUMP_BACK_IN = 6
@ -121,11 +122,8 @@ async function populateJumpBackIn() {
}
})
// fetch each server's data
Promise.all(
servers.map(({ instancePath, address }) =>
servers.forEach(({ instancePath, address }) =>
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address),
),
)
}
@ -150,8 +148,8 @@ async function populateJumpBackIn() {
.slice(0, MAX_JUMP_BACK_IN)
}
async function refreshServer(address: string, instancePath: string) {
await refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
function refreshServer(address: string, instancePath: string) {
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
}
async function joinWorld(world: WorldWithProfile) {

View File

@ -1,7 +1,14 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import type { ServerStatus, ServerWorld, SingleplayerWorld, World } from '@/helpers/worlds.ts'
import { set_world_display_status, getWorldIdentifier } from '@/helpers/worlds.ts'
import type {
ProtocolVersion,
ServerStatus,
ServerWorld,
SingleplayerWorld,
World,
set_world_display_status,
getWorldIdentifier,
} from '@/helpers/worlds.ts'
import { formatNumber, getPingLevel } from '@modrinth/utils'
import {
useRelativeTime,
@ -55,7 +62,7 @@ const props = withDefaults(
playingWorld?: boolean
startingInstance?: boolean
supportsQuickPlay?: boolean
currentProtocol?: number | null
currentProtocol?: ProtocolVersion | null
highlighted?: boolean
// Server only
@ -102,7 +109,8 @@ const serverIncompatible = computed(
!!props.serverStatus &&
!!props.serverStatus.version?.protocol &&
!!props.currentProtocol &&
props.serverStatus.version.protocol !== props.currentProtocol,
(props.serverStatus.version.protocol !== props.currentProtocol.version ||
props.serverStatus.version.legacy !== props.currentProtocol.legacy),
)
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)

View File

@ -51,6 +51,7 @@ export type ServerStatus = {
version?: {
name: string
protocol: number
legacy: boolean
}
favicon?: string
enforces_secure_chat: boolean
@ -70,11 +71,17 @@ export interface Chat {
export type ServerData = {
refreshing: boolean
lastSuccessfulRefresh?: number
status?: ServerStatus
rawMotd?: string | Chat
renderedMotd?: string
}
export type ProtocolVersion = {
version: number
legacy: boolean
}
export async function get_recent_worlds(
limit: number,
displayStatuses?: DisplayStatus[],
@ -156,13 +163,13 @@ export async function remove_server_from_profile(path: string, index: number): P
return await invoke('plugin:worlds|remove_server_from_profile', { path, index })
}
export async function get_profile_protocol_version(path: string): Promise<number | null> {
export async function get_profile_protocol_version(path: string): Promise<ProtocolVersion | null> {
return await invoke('plugin:worlds|get_profile_protocol_version', { path })
}
export async function get_server_status(
address: string,
protocolVersion: number | null = null,
protocolVersion: ProtocolVersion | null = null,
): Promise<ServerStatus> {
return await invoke('plugin:worlds|get_server_status', { address, protocolVersion })
}
@ -206,30 +213,39 @@ export function isServerWorld(world: World): world is ServerWorld {
export async function refreshServerData(
serverData: ServerData,
protocolVersion: number | null,
protocolVersion: ProtocolVersion | null,
address: string,
): Promise<void> {
const refreshTime = Date.now()
serverData.refreshing = true
await get_server_status(address, protocolVersion)
.then((status) => {
if (serverData.lastSuccessfulRefresh && serverData.lastSuccessfulRefresh > refreshTime) {
// Don't update if there was a more recent successful refresh
return
}
serverData.lastSuccessfulRefresh = Date.now()
serverData.status = status
if (status.description) {
serverData.rawMotd = status.description
serverData.renderedMotd = autoToHTML(status.description)
}
})
.catch((err) => {
console.error(`Refreshing addr: ${address}`, err)
})
.finally(() => {
serverData.refreshing = false
})
.catch((err) => {
console.error(`Refreshing addr ${address}`, protocolVersion, err)
if (!protocolVersion?.legacy) {
refreshServerData(serverData, { version: 74, legacy: true }, address)
}
})
}
export async function refreshServers(
export function refreshServers(
worlds: World[],
serverData: Record<string, ServerData>,
protocolVersion: number | null,
protocolVersion: ProtocolVersion | null,
) {
const servers = worlds.filter(isServerWorld)
servers.forEach((server) => {
@ -243,10 +259,8 @@ export async function refreshServers(
})
// noinspection ES6MissingAwait - handled with .then by refreshServerData already
Promise.all(
Object.keys(serverData).map((address) =>
Object.keys(serverData).forEach((address) =>
refreshServerData(serverData[address], protocolVersion, address),
),
)
}

View File

@ -134,6 +134,7 @@ import {
} from '@modrinth/ui'
import { PlusIcon, SpinnerIcon, UpdatedIcon, SearchIcon, XIcon } from '@modrinth/assets'
import {
type ProtocolVersion,
type SingleplayerWorld,
type World,
type ServerWorld,
@ -210,7 +211,9 @@ const worldPlaying = ref<World>()
const worlds = ref<World[]>([])
const serverData = ref<Record<string, ServerData>>({})
const protocolVersion = ref<number | null>(await get_profile_protocol_version(instance.value.path))
const protocolVersion = ref<ProtocolVersion | null>(
await get_profile_protocol_version(instance.value.path),
)
const unlistenProfile = await profile_listener(async (e: ProfileEvent) => {
if (e.profile_path_id !== instance.value.path) return
@ -246,7 +249,7 @@ async function refreshAllWorlds() {
worlds.value = await refreshWorlds(instance.value.path).finally(
() => (refreshingAll.value = false),
)
await refreshServers(worlds.value, serverData.value, protocolVersion.value)
refreshServers(worlds.value, serverData.value, protocolVersion.value)
const hasNoWorlds = worlds.value.length === 0

View File

@ -5,8 +5,8 @@ use tauri::{AppHandle, Manager, Runtime};
use theseus::prelude::ProcessMetadata;
use theseus::profile::{QuickPlayType, get_full_path};
use theseus::worlds::{
DisplayStatus, ServerPackStatus, ServerStatus, World, WorldType,
WorldWithProfile,
DisplayStatus, ProtocolVersion, ServerPackStatus, ServerStatus, World,
WorldType, WorldWithProfile,
};
use theseus::{profile, worlds};
@ -183,14 +183,16 @@ pub async fn remove_server_from_profile(
}
#[tauri::command]
pub async fn get_profile_protocol_version(path: &str) -> Result<Option<i32>> {
pub async fn get_profile_protocol_version(
path: &str,
) -> Result<Option<ProtocolVersion>> {
Ok(worlds::get_profile_protocol_version(path).await?)
}
#[tauri::command]
pub async fn get_server_status(
address: &str,
protocol_version: Option<i32>,
protocol_version: Option<ProtocolVersion>,
) -> Result<ServerStatus> {
Ok(worlds::get_server_status(address, protocol_version).await?)
}

View File

@ -27,6 +27,7 @@ hashlink.workspace = true
png.workspace = true
bytemuck.workspace = true
rgb.workspace = true
phf.workspace = true
chrono = { workspace = true, features = ["serde"] }
daedalus.workspace = true

View File

@ -5,6 +5,8 @@ use crate::state::attached_world_data::AttachedWorldData;
use crate::state::{
Profile, ProfileInstallStage, attached_world_data, server_join_log,
};
use crate::util::protocol_version::OLD_PROTOCOL_VERSIONS;
pub use crate::util::protocol_version::ProtocolVersion;
pub use crate::util::server_ping::{
ServerGameProfile, ServerPlayers, ServerStatus, ServerVersion,
};
@ -835,7 +837,7 @@ mod servers_data {
pub async fn get_profile_protocol_version(
profile: &str,
) -> Result<Option<i32>> {
) -> Result<Option<ProtocolVersion>> {
let mut profile = super::profile::get(profile).await?.ok_or_else(|| {
ErrorKind::UnmanagedProfileError(format!(
"Could not find profile {profile}"
@ -846,7 +848,12 @@ pub async fn get_profile_protocol_version(
}
if let Some(protocol_version) = profile.protocol_version {
return Ok(Some(protocol_version));
return Ok(Some(ProtocolVersion::modern(protocol_version)));
}
if let Some(protocol_version) =
OLD_PROTOCOL_VERSIONS.get(&profile.game_version)
{
return Ok(Some(*protocol_version));
}
let minecraft = crate::api::metadata::get_minecraft_versions().await?;
@ -854,7 +861,7 @@ pub async fn get_profile_protocol_version(
.versions
.iter()
.position(|it| it.id == profile.game_version)
.ok_or(crate::ErrorKind::LauncherError(format!(
.ok_or(ErrorKind::LauncherError(format!(
"Invalid game version: {}",
profile.game_version
)))?;
@ -890,16 +897,19 @@ pub async fn get_profile_protocol_version(
profile.protocol_version = version;
profile.upsert(&state.pool).await?;
}
Ok(version)
Ok(version.map(ProtocolVersion::modern))
}
pub async fn get_server_status(
address: &str,
protocol_version: Option<i32>,
protocol_version: Option<ProtocolVersion>,
) -> Result<ServerStatus> {
let (original_host, original_port) = parse_server_address(address)?;
let (host, port) =
resolve_server_address(original_host, original_port).await?;
tracing::debug!(
"Pinging {address} with protocol version {protocol_version:?}"
);
server_ping::get_server_status(
&(&host as &str, port),
(original_host, original_port),

View File

@ -419,7 +419,7 @@ pub async fn install_minecraft(
pub async fn read_protocol_version_from_jar(
path: PathBuf,
) -> crate::Result<Option<i32>> {
) -> crate::Result<Option<u32>> {
let zip = async_zip::tokio::read::fs::ZipFileReader::new(path).await?;
let Some(entry_index) = zip
.file()
@ -432,7 +432,7 @@ pub async fn read_protocol_version_from_jar(
#[derive(Deserialize, Debug)]
struct VersionData {
protocol_version: Option<i32>,
protocol_version: Option<u32>,
}
let mut data = vec![];

View File

@ -32,7 +32,7 @@ pub struct Profile {
pub icon_path: Option<String>,
pub game_version: String,
pub protocol_version: Option<i32>,
pub protocol_version: Option<u32>,
pub loader: ModLoader,
pub loader_version: Option<String>,
@ -320,7 +320,7 @@ impl TryFrom<ProfileQueryResult> for Profile {
name: x.name,
icon_path: x.icon_path,
game_version: x.game_version,
protocol_version: x.protocol_version.map(|x| x as i32),
protocol_version: x.protocol_version.map(|x| x as u32),
loader: ModLoader::from_string(&x.mod_loader),
loader_version: x.mod_loader_version,
groups: serde_json::from_value(x.groups).unwrap_or_default(),

View File

@ -3,4 +3,5 @@ pub mod fetch;
pub mod io;
pub mod jre;
pub mod platform;
pub mod protocol_version;
pub mod server_ping;

View File

@ -0,0 +1,478 @@
use phf::phf_map;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Copy, Clone)]
pub struct ProtocolVersion {
pub version: u32,
pub legacy: bool,
}
impl ProtocolVersion {
pub const fn modern(version: u32) -> Self {
Self {
version,
legacy: false,
}
}
pub const fn legacy(version: u32) -> Self {
Self {
version,
legacy: true,
}
}
}
/// The map of protocol versions from before version.json was added. For newer versions, the
/// protocol version can just be read from version.json.
pub const OLD_PROTOCOL_VERSIONS: phf::Map<&str, ProtocolVersion> = phf_map! {
// April fools versions
"1.RV-Pre1" => ProtocolVersion::modern(108),
"15w14a" => ProtocolVersion::modern(48),
"2point0_purple" => ProtocolVersion::legacy(92),
"2point0_red" => ProtocolVersion::legacy(91),
"2point0_blue" => ProtocolVersion::legacy(90),
// Normal versions
"18w47a" => ProtocolVersion::modern(446),
"18w46a" => ProtocolVersion::modern(445),
"18w45a" => ProtocolVersion::modern(444),
"18w44a" => ProtocolVersion::modern(443),
"18w43c" => ProtocolVersion::modern(442),
"18w43b" => ProtocolVersion::modern(441),
"18w43a" => ProtocolVersion::modern(440),
"1.13.2" => ProtocolVersion::modern(404),
"1.13.2-pre2" => ProtocolVersion::modern(403),
"1.13.2-pre1" => ProtocolVersion::modern(402),
"1.13.1" => ProtocolVersion::modern(401),
"1.13.1-pre2" => ProtocolVersion::modern(400),
"1.13.1-pre1" => ProtocolVersion::modern(399),
"18w33a" => ProtocolVersion::modern(398),
"18w32a" => ProtocolVersion::modern(397),
"18w31a" => ProtocolVersion::modern(396),
"18w30b" => ProtocolVersion::modern(395),
"18w30a" => ProtocolVersion::modern(394),
"1.13" => ProtocolVersion::modern(393),
"1.13-pre10" => ProtocolVersion::modern(392),
"1.13-pre9" => ProtocolVersion::modern(391),
"1.13-pre8" => ProtocolVersion::modern(390),
"1.13-pre7" => ProtocolVersion::modern(389),
"1.13-pre6" => ProtocolVersion::modern(388),
"1.13-pre5" => ProtocolVersion::modern(387),
"1.13-pre4" => ProtocolVersion::modern(386),
"1.13-pre3" => ProtocolVersion::modern(385),
"1.13-pre2" => ProtocolVersion::modern(384),
"1.13-pre1" => ProtocolVersion::modern(383),
"18w22c" => ProtocolVersion::modern(382),
"18w22b" => ProtocolVersion::modern(381),
"18w22a" => ProtocolVersion::modern(380),
"18w21b" => ProtocolVersion::modern(379),
"18w21a" => ProtocolVersion::modern(378),
"18w20c" => ProtocolVersion::modern(377),
"18w20b" => ProtocolVersion::modern(376),
"18w20a" => ProtocolVersion::modern(375),
"18w19b" => ProtocolVersion::modern(374),
"18w19a" => ProtocolVersion::modern(373),
"18w16a" => ProtocolVersion::modern(372),
"18w15a" => ProtocolVersion::modern(371),
"18w14b" => ProtocolVersion::modern(370),
"18w14a" => ProtocolVersion::modern(369),
"18w11a" => ProtocolVersion::modern(368),
"18w10d" => ProtocolVersion::modern(367),
"18w10c" => ProtocolVersion::modern(366),
"18w10b" => ProtocolVersion::modern(365),
"18w10a" => ProtocolVersion::modern(364),
"18w09a" => ProtocolVersion::modern(363),
"18w08b" => ProtocolVersion::modern(362),
"18w08a" => ProtocolVersion::modern(361),
"18w07c" => ProtocolVersion::modern(360),
"18w07b" => ProtocolVersion::modern(359),
"18w07a" => ProtocolVersion::modern(358),
"18w06a" => ProtocolVersion::modern(357),
"18w05a" => ProtocolVersion::modern(356),
"18w03b" => ProtocolVersion::modern(355),
"18w03a" => ProtocolVersion::modern(354),
"18w02a" => ProtocolVersion::modern(353),
"18w01a" => ProtocolVersion::modern(352),
"17w50a" => ProtocolVersion::modern(351),
"17w49b" => ProtocolVersion::modern(350),
"17w49a" => ProtocolVersion::modern(349),
"17w48a" => ProtocolVersion::modern(348),
"17w47b" => ProtocolVersion::modern(347),
"17w47a" => ProtocolVersion::modern(346),
"17w46a" => ProtocolVersion::modern(345),
"17w45b" => ProtocolVersion::modern(344),
"17w45a" => ProtocolVersion::modern(343),
"17w43b" => ProtocolVersion::modern(342),
"17w43a" => ProtocolVersion::modern(341),
"1.12.2" => ProtocolVersion::modern(340),
"1.21.2-pre2" => ProtocolVersion::modern(339),
"1.21.2-pre1" => ProtocolVersion::modern(339),
"1.12.1" => ProtocolVersion::modern(338),
"1.12.1-pre1" => ProtocolVersion::modern(337),
"17w31a" => ProtocolVersion::modern(336),
"1.12" => ProtocolVersion::modern(335),
"1.12-pre7" => ProtocolVersion::modern(334),
"1.12-pre6" => ProtocolVersion::modern(333),
"1.12-pre5" => ProtocolVersion::modern(332),
"1.12-pre4" => ProtocolVersion::modern(331),
"1.12-pre3" => ProtocolVersion::modern(330),
"1.12-pre2" => ProtocolVersion::modern(329),
"1.12-pre1" => ProtocolVersion::modern(328),
"17w18b" => ProtocolVersion::modern(327),
"17w18a" => ProtocolVersion::modern(326),
"17w17b" => ProtocolVersion::modern(325),
"17w17a" => ProtocolVersion::modern(324),
"17w16b" => ProtocolVersion::modern(323),
"17w16a" => ProtocolVersion::modern(322),
"17w15a" => ProtocolVersion::modern(321),
"17w14a" => ProtocolVersion::modern(320),
"17w13b" => ProtocolVersion::modern(319),
"17w13a" => ProtocolVersion::modern(318),
"17w06a" => ProtocolVersion::modern(317),
"1.11.2" => ProtocolVersion::modern(316),
"1.11.1" => ProtocolVersion::modern(316),
"16w50a" => ProtocolVersion::modern(316),
"1.11" => ProtocolVersion::modern(315),
"1.11-pre1" => ProtocolVersion::modern(314),
"16w44a" => ProtocolVersion::modern(313),
"16w43a" => ProtocolVersion::modern(313),
"16w42a" => ProtocolVersion::modern(312),
"16w41a" => ProtocolVersion::modern(311),
"16w40a" => ProtocolVersion::modern(310),
"16w39c" => ProtocolVersion::modern(309),
"16w39b" => ProtocolVersion::modern(308),
"16w39a" => ProtocolVersion::modern(307),
"16w38a" => ProtocolVersion::modern(306),
"16w36a" => ProtocolVersion::modern(305),
"16w35a" => ProtocolVersion::modern(304),
"16w33a" => ProtocolVersion::modern(303),
"16w32b" => ProtocolVersion::modern(302),
"16w32a" => ProtocolVersion::modern(301),
"1.10.2" => ProtocolVersion::modern(210),
"1.10.1" => ProtocolVersion::modern(210),
"1.10" => ProtocolVersion::modern(210),
"1.10-pre2" => ProtocolVersion::modern(205),
"1.10-pre1" => ProtocolVersion::modern(204),
"16w21b" => ProtocolVersion::modern(203),
"16w21a" => ProtocolVersion::modern(202),
"16w20a" => ProtocolVersion::modern(201),
"1.9.4" => ProtocolVersion::modern(110),
"1.9.3" => ProtocolVersion::modern(110),
"1.9.3-pre3" => ProtocolVersion::modern(110),
"1.9.3-pre2" => ProtocolVersion::modern(110),
"1.9.3-pre1" => ProtocolVersion::modern(109),
"16w15b" => ProtocolVersion::modern(109),
"16w15a" => ProtocolVersion::modern(109),
"16w14a" => ProtocolVersion::modern(109),
"1.9.2" => ProtocolVersion::modern(109),
"1.9.1" => ProtocolVersion::modern(108),
"1.9.1-pre3" => ProtocolVersion::modern(108),
"1.9.1-pre2" => ProtocolVersion::modern(108),
"1.9.1-pre1" => ProtocolVersion::modern(107),
"1.9" => ProtocolVersion::modern(107),
"1.9-pre4" => ProtocolVersion::modern(106),
"1.9-pre3" => ProtocolVersion::modern(105),
"1.9-pre2" => ProtocolVersion::modern(104),
"1.9-pre1" => ProtocolVersion::modern(103),
"16w07b" => ProtocolVersion::modern(102),
"16w07a" => ProtocolVersion::modern(101),
"16w06a" => ProtocolVersion::modern(100),
"16w05b" => ProtocolVersion::modern(99),
"16w05a" => ProtocolVersion::modern(98),
"16w04a" => ProtocolVersion::modern(97),
"16w03a" => ProtocolVersion::modern(96),
"16w02a" => ProtocolVersion::modern(95),
"15w51b" => ProtocolVersion::modern(94),
"15w51a" => ProtocolVersion::modern(93),
"15w50a" => ProtocolVersion::modern(92),
"15w49b" => ProtocolVersion::modern(91),
"15w49a" => ProtocolVersion::modern(90),
"15w47c" => ProtocolVersion::modern(89),
"15w47b" => ProtocolVersion::modern(88),
"15w47a" => ProtocolVersion::modern(87),
"15w46a" => ProtocolVersion::modern(86),
"15w45a" => ProtocolVersion::modern(85),
"15w44b" => ProtocolVersion::modern(84),
"15w44a" => ProtocolVersion::modern(83),
"15w43c" => ProtocolVersion::modern(82),
"15w43b" => ProtocolVersion::modern(81),
"15w43a" => ProtocolVersion::modern(80),
"15w42a" => ProtocolVersion::modern(79),
"15w41b" => ProtocolVersion::modern(78),
"15w41a" => ProtocolVersion::modern(77),
"15w40b" => ProtocolVersion::modern(76),
"15w40a" => ProtocolVersion::modern(75),
"15w39c" => ProtocolVersion::modern(74),
"15w39b" => ProtocolVersion::modern(74),
"15w39a" => ProtocolVersion::modern(74),
"15w38b" => ProtocolVersion::modern(73),
"15w38a" => ProtocolVersion::modern(72),
"15w37a" => ProtocolVersion::modern(71),
"15w36d" => ProtocolVersion::modern(70),
"15w36c" => ProtocolVersion::modern(69),
"15w36b" => ProtocolVersion::modern(68),
"15w36a" => ProtocolVersion::modern(67),
"15w35e" => ProtocolVersion::modern(66),
"15w35d" => ProtocolVersion::modern(65),
"15w35c" => ProtocolVersion::modern(64),
"15w35b" => ProtocolVersion::modern(63),
"15w35a" => ProtocolVersion::modern(62),
"15w34d" => ProtocolVersion::modern(61),
"15w34c" => ProtocolVersion::modern(60),
"15w34b" => ProtocolVersion::modern(59),
"15w34a" => ProtocolVersion::modern(58),
"15w33c" => ProtocolVersion::modern(57),
"15w33b" => ProtocolVersion::modern(56),
"15w33a" => ProtocolVersion::modern(55),
"15w32c" => ProtocolVersion::modern(54),
"15w32b" => ProtocolVersion::modern(53),
"15w32a" => ProtocolVersion::modern(52),
"15w31c" => ProtocolVersion::modern(51),
"15w31b" => ProtocolVersion::modern(50),
"15w31a" => ProtocolVersion::modern(49),
"1.8.9" => ProtocolVersion::modern(47),
"1.8.8" => ProtocolVersion::modern(47),
"1.8.7" => ProtocolVersion::modern(47),
"1.8.6" => ProtocolVersion::modern(47),
"1.8.5" => ProtocolVersion::modern(47),
"1.8.4" => ProtocolVersion::modern(47),
"1.8.3" => ProtocolVersion::modern(47),
"1.8.2" => ProtocolVersion::modern(47),
"1.8.2-pre7" => ProtocolVersion::modern(47),
"1.8.2-pre6" => ProtocolVersion::modern(47),
"1.8.2-pre5" => ProtocolVersion::modern(47),
"1.8.2-pre4" => ProtocolVersion::modern(47),
"1.8.2-pre3" => ProtocolVersion::modern(47),
"1.8.2-pre2" => ProtocolVersion::modern(47),
"1.8.2-pre1" => ProtocolVersion::modern(47),
"1.8.1" => ProtocolVersion::modern(47),
"1.8.1-pre5" => ProtocolVersion::modern(47),
"1.8.1-pre4" => ProtocolVersion::modern(47),
"1.8.1-pre3" => ProtocolVersion::modern(47),
"1.8.1-pre2" => ProtocolVersion::modern(47),
"1.8.1-pre1" => ProtocolVersion::modern(47),
"1.8" => ProtocolVersion::modern(47),
"1.8-pre3" => ProtocolVersion::modern(46),
"1.8-pre2" => ProtocolVersion::modern(45),
"1.8-pre1" => ProtocolVersion::modern(44),
"14w34d" => ProtocolVersion::modern(43),
"14w34c" => ProtocolVersion::modern(42),
"14w34b" => ProtocolVersion::modern(41),
"14w34a" => ProtocolVersion::modern(40),
"14w33c" => ProtocolVersion::modern(39),
"14w33b" => ProtocolVersion::modern(38),
"14w33a" => ProtocolVersion::modern(37),
"14w32d" => ProtocolVersion::modern(36),
"14w32c" => ProtocolVersion::modern(35),
"14w32b" => ProtocolVersion::modern(34),
"14w32a" => ProtocolVersion::modern(33),
"14w31a" => ProtocolVersion::modern(32),
"14w30c" => ProtocolVersion::modern(31),
"14w30b" => ProtocolVersion::modern(30),
"14w30a" => ProtocolVersion::modern(30),
"14w29b" => ProtocolVersion::modern(29),
"14w29a" => ProtocolVersion::modern(29),
"14w28b" => ProtocolVersion::modern(28),
"14w28a" => ProtocolVersion::modern(27),
"14w27b" => ProtocolVersion::modern(26),
"14w27a" => ProtocolVersion::modern(26),
"14w26c" => ProtocolVersion::modern(25),
"14w26b" => ProtocolVersion::modern(24),
"14w26a" => ProtocolVersion::modern(23),
"14w25b" => ProtocolVersion::modern(22),
"14w25a" => ProtocolVersion::modern(21),
"14w21b" => ProtocolVersion::modern(20),
"14w21a" => ProtocolVersion::modern(19),
"14w20b" => ProtocolVersion::modern(18),
"14w20a" => ProtocolVersion::modern(18),
"14w19a" => ProtocolVersion::modern(17),
"14w18b" => ProtocolVersion::modern(16),
"14w18a" => ProtocolVersion::modern(16),
"14w17a" => ProtocolVersion::modern(15),
"14w11b" => ProtocolVersion::modern(14),
"14w11a" => ProtocolVersion::modern(14),
"14w10c" => ProtocolVersion::modern(13),
"14w10b" => ProtocolVersion::modern(13),
"14w10a" => ProtocolVersion::modern(13),
"14w08a" => ProtocolVersion::modern(12),
"14w07a" => ProtocolVersion::modern(11),
"14w06b" => ProtocolVersion::modern(10),
"14w06a" => ProtocolVersion::modern(10),
"14w05b" => ProtocolVersion::modern(9),
"14w05a" => ProtocolVersion::modern(9),
"14w04b" => ProtocolVersion::modern(8),
"14w04a" => ProtocolVersion::modern(7),
"14w03b" => ProtocolVersion::modern(6),
"14w03a" => ProtocolVersion::modern(6),
"14w02c" => ProtocolVersion::modern(5),
"14w02b" => ProtocolVersion::modern(5),
"14w02a" => ProtocolVersion::modern(5),
"1.7.10" => ProtocolVersion::modern(5),
"1.7.10-pre4" => ProtocolVersion::modern(5),
"1.7.10-pre3" => ProtocolVersion::modern(5),
"1.7.10-pre2" => ProtocolVersion::modern(5),
"1.7.10-pre1" => ProtocolVersion::modern(5),
"1.7.9" => ProtocolVersion::modern(5),
"1.7.8" => ProtocolVersion::modern(5),
"1.7.7" => ProtocolVersion::modern(5),
"1.7.6" => ProtocolVersion::modern(5),
"1.7.6-pre2" => ProtocolVersion::modern(5),
"1.7.6-pre1" => ProtocolVersion::modern(5),
"1.7.5" => ProtocolVersion::modern(4),
"1.7.4" => ProtocolVersion::modern(4),
"1.7.3" => ProtocolVersion::modern(4),
"13w49a" => ProtocolVersion::modern(4),
"13w48b" => ProtocolVersion::modern(4),
"13w48a" => ProtocolVersion::modern(4),
"13w47e" => ProtocolVersion::modern(4),
"13w47d" => ProtocolVersion::modern(4),
"13w47c" => ProtocolVersion::modern(4),
"13w47b" => ProtocolVersion::modern(4),
"13w47a" => ProtocolVersion::modern(4),
"1.7.2" => ProtocolVersion::modern(4),
"1.7.1" => ProtocolVersion::modern(3),
"1.7" => ProtocolVersion::modern(3),
"13w43a" => ProtocolVersion::modern(2),
"13w42b" => ProtocolVersion::modern(1),
"13w42a" => ProtocolVersion::modern(1),
"13w41b" => ProtocolVersion::modern(0),
"13w41a" => ProtocolVersion::modern(0),
"13w39b" => ProtocolVersion::legacy(80),
"13w39a" => ProtocolVersion::legacy(80),
"13w38c" => ProtocolVersion::legacy(79),
"13w38b" => ProtocolVersion::legacy(79),
"13w38a" => ProtocolVersion::legacy(79),
"1.6.4" => ProtocolVersion::legacy(78),
"1.6.3" => ProtocolVersion::legacy(77),
"13w37b" => ProtocolVersion::legacy(76),
"13w37a" => ProtocolVersion::legacy(76),
"13w36b" => ProtocolVersion::legacy(75),
"13w36a" => ProtocolVersion::legacy(75),
"1.6.2" => ProtocolVersion::legacy(74),
"1.6.1" => ProtocolVersion::legacy(73),
"1.6" => ProtocolVersion::legacy(72),
"13w26a" => ProtocolVersion::legacy(72),
"13w25c" => ProtocolVersion::legacy(71),
"13w25b" => ProtocolVersion::legacy(71),
"13w25a" => ProtocolVersion::legacy(71),
"13w24b" => ProtocolVersion::legacy(70),
"13w24a" => ProtocolVersion::legacy(69),
"13w23b" => ProtocolVersion::legacy(68),
"13w23a" => ProtocolVersion::legacy(67),
"13w22a" => ProtocolVersion::legacy(67),
"13w21b" => ProtocolVersion::legacy(67),
"13w21a" => ProtocolVersion::legacy(67),
"13w19a" => ProtocolVersion::legacy(66),
"13w18c" => ProtocolVersion::legacy(65),
"13w18b" => ProtocolVersion::legacy(65),
"13w18a" => ProtocolVersion::legacy(65),
"13w17a" => ProtocolVersion::legacy(64),
"13w16b" => ProtocolVersion::legacy(63),
"13w16a" => ProtocolVersion::legacy(62),
"1.5.2" => ProtocolVersion::legacy(61),
"1.5.1" => ProtocolVersion::legacy(60),
"13w12~" => ProtocolVersion::legacy(60),
"13w11a" => ProtocolVersion::legacy(60),
"1.5" => ProtocolVersion::legacy(60),
"13w10b" => ProtocolVersion::legacy(60),
"13w10a" => ProtocolVersion::legacy(60),
"13w09c" => ProtocolVersion::legacy(60),
"13w09b" => ProtocolVersion::legacy(59),
"13w09a" => ProtocolVersion::legacy(59),
"13w07a" => ProtocolVersion::legacy(58),
"13w06a" => ProtocolVersion::legacy(58),
"13w05b" => ProtocolVersion::legacy(56),
"13w05a" => ProtocolVersion::legacy(56),
"13w04a" => ProtocolVersion::legacy(55),
"13w03a" => ProtocolVersion::legacy(54),
"13w02b" => ProtocolVersion::legacy(53),
"13w02a" => ProtocolVersion::legacy(53),
"13w01b" => ProtocolVersion::legacy(52),
"13w01a" => ProtocolVersion::legacy(52),
"1.4.7" => ProtocolVersion::legacy(51),
"1.4.6" => ProtocolVersion::legacy(51),
"12w50b" => ProtocolVersion::legacy(51),
"12w50a" => ProtocolVersion::legacy(51),
"12w49a" => ProtocolVersion::legacy(50),
"1.4.5" => ProtocolVersion::legacy(49),
"1.4.4" => ProtocolVersion::legacy(49),
"1.4.3" => ProtocolVersion::legacy(48),
"1.4.2" => ProtocolVersion::legacy(47),
"1.4.1" => ProtocolVersion::legacy(47),
"1.4" => ProtocolVersion::legacy(47),
"12w42b" => ProtocolVersion::legacy(47),
"12w42a" => ProtocolVersion::legacy(46),
"12w41b" => ProtocolVersion::legacy(46),
"12w41a" => ProtocolVersion::legacy(46),
"12w40b" => ProtocolVersion::legacy(45),
"12w40a" => ProtocolVersion::legacy(44),
"12w39b" => ProtocolVersion::legacy(43),
"12w39a" => ProtocolVersion::legacy(43),
"12w38b" => ProtocolVersion::legacy(43),
"12w38a" => ProtocolVersion::legacy(43),
"12w37a" => ProtocolVersion::legacy(42),
"12w36a" => ProtocolVersion::legacy(42),
"12w34b" => ProtocolVersion::legacy(42),
"12w34a" => ProtocolVersion::legacy(41),
"12w32a" => ProtocolVersion::legacy(40),
"1.3.2" => ProtocolVersion::legacy(39),
"1.3.1" => ProtocolVersion::legacy(39),
"1.3" => ProtocolVersion::legacy(39),
"12w30e" => ProtocolVersion::legacy(39),
"12w30d" => ProtocolVersion::legacy(39),
"12w30c" => ProtocolVersion::legacy(39),
"12w30b" => ProtocolVersion::legacy(38),
"12w30a" => ProtocolVersion::legacy(38),
"12w27a" => ProtocolVersion::legacy(38),
"12w26a" => ProtocolVersion::legacy(37),
"12w25a" => ProtocolVersion::legacy(37),
"12w24a" => ProtocolVersion::legacy(36),
"12w23b" => ProtocolVersion::legacy(35),
"12w23a" => ProtocolVersion::legacy(35),
"12w22a" => ProtocolVersion::legacy(34),
"12w21b" => ProtocolVersion::legacy(33),
"12w21a" => ProtocolVersion::legacy(33),
"12w19a" => ProtocolVersion::legacy(32),
"12w18a" => ProtocolVersion::legacy(32),
"12w17a" => ProtocolVersion::legacy(31),
"12w16a" => ProtocolVersion::legacy(30),
"12w15a" => ProtocolVersion::legacy(29),
"1.2.5" => ProtocolVersion::legacy(29),
"1.2.4" => ProtocolVersion::legacy(29),
"1.2.3" => ProtocolVersion::legacy(28),
"1.2.2" => ProtocolVersion::legacy(28),
"1.2.1" => ProtocolVersion::legacy(28),
"1.2" => ProtocolVersion::legacy(28),
"12w08a" => ProtocolVersion::legacy(28),
"12w07b" => ProtocolVersion::legacy(27),
"12w07a" => ProtocolVersion::legacy(27),
"12w06a" => ProtocolVersion::legacy(25),
"12w05b" => ProtocolVersion::legacy(24),
"12w05a" => ProtocolVersion::legacy(24),
"12w04a" => ProtocolVersion::legacy(24),
"12w03a" => ProtocolVersion::legacy(24),
"1.1" => ProtocolVersion::legacy(23),
"12w01a" => ProtocolVersion::legacy(23),
"11w50a" => ProtocolVersion::legacy(22),
"11w49a" => ProtocolVersion::legacy(22),
"11w48a" => ProtocolVersion::legacy(22),
"11w47a" => ProtocolVersion::legacy(22),
"1.0.1" => ProtocolVersion::legacy(22),
"1.0.0" => ProtocolVersion::legacy(22),
"1.0.0-rc2-1" => ProtocolVersion::legacy(22),
"1.0.0-rc2-2" => ProtocolVersion::legacy(22),
"1.0.0-rc2-3" => ProtocolVersion::legacy(22),
"1.0.0-rc1" => ProtocolVersion::legacy(22),
"b1.9-pre6" => ProtocolVersion::legacy(22),
"b1.9-pre5" => ProtocolVersion::legacy(21),
"b1.9-pre4" => ProtocolVersion::legacy(20),
"b1.9-pre3" => ProtocolVersion::legacy(19),
"b1.9-pre2" => ProtocolVersion::legacy(19),
"b1.9-pre1" => ProtocolVersion::legacy(18),
"b1.8.1" => ProtocolVersion::legacy(17),
"b1.8" => ProtocolVersion::legacy(17),
"b1.8-pre2" => ProtocolVersion::legacy(16),
"b1.8-pre1-1" => ProtocolVersion::legacy(15),
"b1.8-pre1-2" => ProtocolVersion::legacy(15),
};

View File

@ -1,5 +1,6 @@
use crate::ErrorKind;
use crate::error::Result;
use crate::util::protocol_version::ProtocolVersion;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use std::time::Duration;
@ -42,16 +43,23 @@ pub struct ServerGameProfile {
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ServerVersion {
pub name: String,
pub protocol: i32,
pub protocol: u32,
#[serde(skip_deserializing)]
pub legacy: bool,
}
pub async fn get_server_status(
address: &impl ToSocketAddrs,
original_address: (&str, u16),
protocol_version: Option<i32>,
protocol_version: Option<ProtocolVersion>,
) -> Result<ServerStatus> {
select! {
res = modern::status(address, original_address, protocol_version) => res,
res = async {
match protocol_version {
Some(ProtocolVersion { legacy: true, version }) => legacy::status(address, original_address, Some(version as u8)).await,
protocol => modern::status(address, original_address, protocol.map(|v| v.version)).await,
}
} => res,
_ = tokio::time::sleep(Duration::from_secs(30)) => Err(ErrorKind::OtherError(
format!("Ping of {}:{} timed out", original_address.0, original_address.1)
).into())
@ -68,7 +76,7 @@ mod modern {
pub async fn status(
address: &impl ToSocketAddrs,
original_address: (&str, u16),
protocol_version: Option<i32>,
protocol_version: Option<u32>,
) -> crate::Result<ServerStatus> {
let mut stream = TcpStream::connect(address).await?;
handshake(&mut stream, original_address, protocol_version).await?;
@ -80,10 +88,10 @@ mod modern {
async fn handshake(
stream: &mut TcpStream,
original_address: (&str, u16),
protocol_version: Option<i32>,
protocol_version: Option<u32>,
) -> crate::Result<()> {
let (host, port) = original_address;
let protocol_version = protocol_version.unwrap_or(-1);
let protocol_version = protocol_version.map_or(-1, |x| x as i32);
const PACKET_ID: i32 = 0;
const NEXT_STATE: i32 = 1;
@ -221,3 +229,95 @@ mod modern {
}
}
}
mod legacy {
use super::ServerStatus;
use crate::worlds::{ServerPlayers, ServerVersion};
use crate::{Error, ErrorKind};
use serde_json::value::to_raw_value;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpStream, ToSocketAddrs};
pub async fn status(
address: &impl ToSocketAddrs,
original_address: (&str, u16),
protocol_version: Option<u8>,
) -> crate::Result<ServerStatus> {
let protocol_version = protocol_version.unwrap_or(74);
let mut packet = vec![0xfe];
if protocol_version >= 47 {
packet.push(0x01);
}
if protocol_version >= 73 {
packet.push(0xfa);
write_legacy(&mut packet, "MC|PingHost");
let (host, port) = original_address;
let len_index = packet.len();
packet.push(protocol_version);
write_legacy(&mut packet, host);
packet.extend_from_slice(&(port as u32).to_be_bytes());
packet.splice(
len_index..len_index,
((packet.len() - len_index) as u16).to_be_bytes(),
);
}
let mut stream = TcpStream::connect(address).await?;
stream.write_all(&packet).await?;
stream.flush().await?;
let packet_id = stream.read_u8().await?;
if packet_id != 0xff {
return Err(Error::from(ErrorKind::InputError(
"Unexpected legacy status response".to_string(),
)));
}
let data_length = stream.read_u16().await?;
let mut data = vec![0u8; data_length as usize * 2];
stream.read_exact(&mut data).await?;
drop(stream);
let data = String::from_utf16_lossy(
&data
.chunks_exact(2)
.map(|a| u16::from_be_bytes([a[0], a[1]]))
.collect::<Vec<u16>>(),
);
let mut ancient_server = false;
let mut parts = data.split('\0');
if parts.next() != Some("§1") {
ancient_server = true;
parts = data.split('§');
}
Ok(ServerStatus {
version: (!ancient_server).then(|| ServerVersion {
protocol: parts
.next()
.and_then(|x| x.parse().ok())
.unwrap_or(0),
name: parts.next().unwrap_or("").to_owned(),
legacy: true,
}),
description: parts.next().and_then(|x| to_raw_value(x).ok()),
players: Some(ServerPlayers {
online: parts.next().and_then(|x| x.parse().ok()).unwrap_or(-1),
max: parts.next().and_then(|x| x.parse().ok()).unwrap_or(-1),
sample: vec![],
}),
favicon: None,
enforces_secure_chat: false,
ping: None,
})
}
fn write_legacy(out: &mut Vec<u8>, text: &str) {
let encoded = text.encode_utf16().collect::<Vec<_>>();
out.extend_from_slice(&(encoded.len() as u16).to_be_bytes());
out.extend(encoded.into_iter().flat_map(u16::to_be_bytes));
}
}