feat(shared): support server-pushed resource packs

This commit is contained in:
Serkyo 2026-07-12 15:49:33 +02:00
parent 5c61ecc209
commit 339c0efe5a
2 changed files with 11 additions and 9 deletions

View file

@ -1,6 +1,4 @@
//! Network protocol types and constants. //! Network protocol types and constants.
//!
//! This module defines the pure, serde-serializable messages used for network communication between the client and server. It contains no networking logic or async dependencies.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -42,9 +40,9 @@ pub struct HandshakeAck {
pub protocol_version: u32, pub protocol_version: u32,
/// Human-readable server build string. /// Human-readable server build string.
pub server_build: String, pub server_build: String,
/// Packs the world requires, each with an optional download source. Empty in M1. /// Packs the world requires, each with an optional download source. May include `PackTier::Resource` entries (a server resource pack), which are delivered one-way and applied client-side rather than strict-matched; a consumer must branch on tier (or `PackTier::requires_strict_match`) before treating an entry as a match requirement.
pub world_packs: Vec<RequiredPack>, pub world_packs: Vec<RequiredPack>,
/// Packs the client is missing relative to the server, each with an optional download source. Empty in M1. /// Packs the client is missing relative to the server, each with an optional download source. As with `world_packs`, `PackTier::Resource` entries are delivered, not matched.
pub missing_packs: Vec<RequiredPack>, pub missing_packs: Vec<RequiredPack>,
/// Which stream carries which purpose for this session. /// Which stream carries which purpose for this session.
pub stream_layout: StreamLayout, pub stream_layout: StreamLayout,
@ -77,6 +75,8 @@ pub enum RejectReason {
ProtocolMismatch, ProtocolMismatch,
/// Client is missing required packs or has incompatible versions. /// Client is missing required packs or has incompatible versions.
PackMismatch, PackMismatch,
/// Client declined or failed to fetch a server resource pack the server marked required.
ResourcePackDeclined,
/// Client failed to authenticate. /// Client failed to authenticate.
AuthFailed, AuthFailed,
/// Client is banned from the server. /// Client is banned from the server.
@ -96,8 +96,6 @@ pub struct PlayerIdentity {
} }
/// Reference to a content pack (resource pack, data pack, or Lua mod) as it appears in a modlist exchanged during the handshake. /// Reference to a content pack (resource pack, data pack, or Lua mod) as it appears in a modlist exchanged during the handshake.
///
/// Identity is the pair (`id`, `content_hash`): `id` names the pack and `content_hash` is the authoritative value compared when deciding whether two installations agree. `version` is informational (display, logs, upgrade prompts) and is **not** the match key — two builds sharing a version but differing in contents are distinct packs.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PackRef { pub struct PackRef {
/// Namespaced content identifier of the pack (`namespace:id`). Charset validation is deferred to the modlist-matching concept (out of M1 scope). /// Namespaced content identifier of the pack (`namespace:id`). Charset validation is deferred to the modlist-matching concept (out of M1 scope).
@ -114,7 +112,7 @@ pub struct PackRef {
/// Classification of a content pack, determining the handshake matching rule applied to it. Inferred from folder contents, not self-declared: `assets/`-only is a resource pack, `data/`-only is a data pack, presence of `scripts/` is a Lua mod. /// Classification of a content pack, determining the handshake matching rule applied to it. Inferred from folder contents, not self-declared: `assets/`-only is a resource pack, `data/`-only is a data pack, presence of `scripts/` is a Lua mod.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PackTier { pub enum PackTier {
/// Client-only asset overlay (`assets/` only). Never streamed by the server, never matched. /// Client-side asset overlay (`assets/` only). Never strict-matched between peers. A server may push one server resource pack of its own, delivered one-way and applied on top of the client's local pack stack; enforcement of a `required` server pack is apply-or-reject at the client, not a peer hash-match.
Resource, Resource,
/// Declarative content (`data/` only). Must match exactly between peers. /// Declarative content (`data/` only). Must match exactly between peers.
Data, Data,
@ -126,7 +124,7 @@ pub enum PackTier {
} }
impl PackTier { impl PackTier {
/// Returns whether a pack of this tier must match byte-for-byte between client and server for the connection to be accepted. Resource packs are never matched; data packs and non-`client_only` mods must match exactly (see Load order § modlist matching). /// Returns whether a pack of this tier must match byte-for-byte between client and server for the connection to be accepted. Resource packs are never matched; data packs and non-`client_only` mods must match exactly. A `false` here does not imply the server never sends the pack, a server resource pack is delivered one-way despite not being part of bidirectional matching.
#[must_use] #[must_use]
pub fn requires_strict_match(self) -> bool { pub fn requires_strict_match(self) -> bool {
match self { match self {
@ -144,6 +142,8 @@ pub struct RequiredPack {
pub pack: PackRef, pub pack: PackRef,
/// Optional HTTP(S) URL to fetch the pack from, bypassing the QUIC asset stream for large downloads. `None` means fetch over the asset stream. /// Optional HTTP(S) URL to fetch the pack from, bypassing the QUIC asset stream for large downloads. `None` means fetch over the asset stream.
pub download_url: Option<String>, pub download_url: Option<String>,
/// Whether the connection is rejected if the client cannot obtain and apply this pack. For data/mod tiers this is always `true` (they are mandatory for a correct session). For a `PackTier::Resource` entry (a server resource pack) it distinguishes an *optional* overlay the client may decline and keep playing (`false`) from a *required* one whose decline or fetch failure rejects the connection (`true`).
pub required: bool,
} }
/// Optional protocol feature bits. /// Optional protocol feature bits.

View file

@ -6,7 +6,9 @@ Two distinct, orthogonal systems. They are kept separate and are not collapsed i
Client-side asset overlays: textures, sounds, models, fonts, language files. No logic. Client-side asset overlays: textures, sounds, models, fonts, language files. No logic.
A pack is a directory tree mirroring `/assets/` that overrides files by path. The renderer/asset loader resolves logical asset IDs against a stack of pack roots (base game → installed packs by priority) and the topmost hit wins. The server has no involvement. Ownership sits with the asset pipeline (in `client`, or a sibling `assets` crate if it grows). Pack authors never touch Lua. A pack is a directory tree mirroring `/assets/` that overrides files by path. The renderer/asset loader resolves logical asset IDs against a stack of pack roots (base game → installed packs by priority) and the topmost hit wins. Ownership sits with the asset pipeline (in `client`, or a sibling `assets` crate if it grows). Pack authors never touch Lua.
A client's own resource packs are a purely local choice; the server has no say over them and they are never part of gameplay modlist matching. The **one** exception is a **server resource pack**: a server may push a single cosmetic overlay of its own (a themed / total-conversion server) to connecting clients. It is a one-way server → client push, applied on top of the client's local stack, and enforced per the server's choice — *optional* packs the client may decline and keep playing, a *required* pack the client declines or fails to fetch rejects the connection. It is still `assets/`-only (no `data/`, no `scripts/`), so it can never affect authoritative state. Fetch and enforcement semantics live in the vault's `Architecture/Load order.md` § Streaming.
## Data packs ## Data packs