mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(bedrock): add resource pack support
This commit is contained in:
@@ -319,7 +319,7 @@ impl LoadConfiguration for AdvancedConfiguration {
|
||||
}
|
||||
|
||||
fn validate(&self) {
|
||||
self.resource_pack.validate();
|
||||
//self.resource_pack.validate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Configuration for server resource pack distribution.
|
||||
///
|
||||
/// Controls whether a resource pack is offered or enforced,
|
||||
/// along with its metadata and client prompt behaviour.
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct ResourcePackConfig {
|
||||
pub java: JavaResourcePackConfig,
|
||||
pub bedrock: BedrockResourcePackConfig,
|
||||
}
|
||||
|
||||
/// Java-specific resource pack configuration (Single URL/Hash)
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct JavaResourcePackConfig {
|
||||
/// Whether the resource pack system is enabled.
|
||||
pub enabled: bool,
|
||||
/// The URL to the resource pack.
|
||||
@@ -19,23 +24,33 @@ pub struct ResourcePackConfig {
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
impl ResourcePackConfig {
|
||||
pub fn validate(&self) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
}
|
||||
/// Bedrock-specific configuration (Supports multiple local/remote packs)
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct BedrockResourcePackConfig {
|
||||
pub enabled: bool,
|
||||
/// If true, players cannot join without accepting packs.
|
||||
pub force: bool,
|
||||
/// List of packs to be sent to the client.
|
||||
pub packs: Vec<BedrockPack>,
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
!self.url.is_empty(),
|
||||
!self.sha1.is_empty(),
|
||||
"Resource pack path or SHA1 hash is missing"
|
||||
);
|
||||
|
||||
let hash_len = self.sha1.len();
|
||||
|
||||
assert_eq!(
|
||||
hash_len, 40,
|
||||
"Resource pack SHA1 hash is the wrong length (should be 40, is {hash_len})"
|
||||
);
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct BedrockPack {
|
||||
pub uuid: Uuid,
|
||||
pub version: String,
|
||||
pub size: u64,
|
||||
pub download_url: String,
|
||||
#[serde(default)]
|
||||
pub content_key: String,
|
||||
#[serde(default)]
|
||||
pub sub_pack_name: String,
|
||||
#[serde(default)]
|
||||
pub content_id: String,
|
||||
#[serde(default)]
|
||||
pub has_scripts: bool,
|
||||
#[serde(default)]
|
||||
pub addon_pack: bool,
|
||||
#[serde(default)]
|
||||
pub rtx_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ pub mod set_time;
|
||||
pub mod set_title;
|
||||
pub mod start_game;
|
||||
pub mod update_abilities;
|
||||
pub mod update_artributes;
|
||||
pub mod update_attributes;
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
use crate::{bedrock::client::start_game::Experiments, serial::PacketWrite};
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
use crate::{
|
||||
bedrock::client::start_game::Experiments, codec::var_uint::VarUInt, serial::PacketWrite,
|
||||
};
|
||||
#[derive(PacketWrite)]
|
||||
pub struct ResourcePackStackEntry {
|
||||
pub uuid: String,
|
||||
pub version: String,
|
||||
pub sub_pack_name: String,
|
||||
}
|
||||
|
||||
#[derive(PacketWrite)]
|
||||
#[packet(7)]
|
||||
pub struct CResourcePackStackPacket {
|
||||
// https://mojang.github.io/bedrock-protocol-docs/html/ResourcePackStackPacket.html
|
||||
resource_pack_required: bool,
|
||||
texture_pack_list_size: VarUInt,
|
||||
game_version: String,
|
||||
experiments: Experiments,
|
||||
/// When connecting to an Editor world, include the vanilla editor packs in the stack
|
||||
include_editor_packs: bool,
|
||||
pub resource_pack_required: bool,
|
||||
pub resource_packs: Vec<ResourcePackStackEntry>,
|
||||
pub game_version: String,
|
||||
pub experiments: Experiments,
|
||||
pub include_editor_packs: bool,
|
||||
}
|
||||
|
||||
impl CResourcePackStackPacket {
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
resource_pack_required: bool,
|
||||
texture_pack_list_size: VarUInt,
|
||||
resource_packs: Vec<ResourcePackStackEntry>,
|
||||
game_version: String,
|
||||
experiments: Experiments,
|
||||
include_editor_packs: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource_pack_required,
|
||||
texture_pack_list_size,
|
||||
resource_packs,
|
||||
game_version,
|
||||
experiments,
|
||||
include_editor_packs,
|
||||
|
||||
@@ -1,39 +1,62 @@
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
use crate::serial::PacketWrite;
|
||||
|
||||
#[derive(PacketWrite)]
|
||||
#[packet(6)]
|
||||
pub struct CResourcePacksInfo {
|
||||
resource_pack_required: bool,
|
||||
has_addon_packs: bool,
|
||||
has_scripts: bool,
|
||||
is_vibrant_visuals_force_disabled: bool,
|
||||
world_template_id: uuid::Uuid,
|
||||
world_template_version: String,
|
||||
resource_packs_size: u16, // TODO: Add more
|
||||
use pumpkin_macros::packet;
|
||||
use std::io::{Error, Write};
|
||||
pub struct ResourcePackEntry {
|
||||
pub uuid: String,
|
||||
pub version: String,
|
||||
pub size: u64,
|
||||
pub content_key: String,
|
||||
pub sub_pack_name: String,
|
||||
pub content_id: String,
|
||||
pub has_scripts: bool,
|
||||
pub addon_pack: bool,
|
||||
pub rtx_enabled: bool,
|
||||
pub download_url: String,
|
||||
}
|
||||
|
||||
impl CResourcePacksInfo {
|
||||
#[must_use]
|
||||
#[expect(clippy::fn_params_excessive_bools)]
|
||||
pub const fn new(
|
||||
resource_pack_required: bool,
|
||||
has_addon_packs: bool,
|
||||
has_scripts: bool,
|
||||
is_vibrant_visuals_force_disabled: bool,
|
||||
world_template_id: uuid::Uuid,
|
||||
world_template_version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
resource_pack_required,
|
||||
has_addon_packs,
|
||||
has_scripts,
|
||||
is_vibrant_visuals_force_disabled,
|
||||
world_template_id,
|
||||
world_template_version,
|
||||
// TODO
|
||||
resource_packs_size: 0,
|
||||
impl PacketWrite for ResourcePackEntry {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.uuid.write(writer)?;
|
||||
self.version.write(writer)?;
|
||||
// Bedrock uses Little Endian u64 for size
|
||||
writer.write_all(&self.size.to_le_bytes())?;
|
||||
self.content_key.write(writer)?;
|
||||
self.sub_pack_name.write(writer)?;
|
||||
self.content_id.write(writer)?;
|
||||
self.has_scripts.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[packet(6)]
|
||||
pub struct CResourcePacksInfo {
|
||||
pub resource_pack_required: bool,
|
||||
pub has_addon_packs: bool,
|
||||
pub has_scripts: bool,
|
||||
pub is_vibrant_visuals_force_disabled: bool,
|
||||
pub world_template_id: uuid::Uuid,
|
||||
pub world_template_version: String,
|
||||
pub resource_packs: Vec<ResourcePackEntry>,
|
||||
}
|
||||
|
||||
impl PacketWrite for CResourcePacksInfo {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.resource_pack_required.write(writer)?;
|
||||
self.has_addon_packs.write(writer)?;
|
||||
self.has_scripts.write(writer)?;
|
||||
self.is_vibrant_visuals_force_disabled.write(writer)?;
|
||||
|
||||
self.world_template_id.write(writer)?;
|
||||
|
||||
self.world_template_version.write(writer)?;
|
||||
|
||||
let len = self.resource_packs.len() as u16;
|
||||
writer.write_all(&len.to_le_bytes())?;
|
||||
|
||||
for entry in &self.resource_packs {
|
||||
entry.write(writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ pub async fn send_attribute_updates_for_living(
|
||||
living: &crate::entity::living::LivingEntity,
|
||||
attributes: Vec<Attributes>,
|
||||
) {
|
||||
use pumpkin_protocol::bedrock::client::update_artributes::{
|
||||
use pumpkin_protocol::bedrock::client::update_attributes::{
|
||||
Attribute as BeAttribute, CUpdateAttributes as BePacket,
|
||||
};
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
|
||||
@@ -2,20 +2,18 @@ use crate::{
|
||||
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
||||
server::Server,
|
||||
};
|
||||
use pumpkin_protocol::bedrock::server::{
|
||||
login::ClientData, resource_pack_response::SResourcePackResponse,
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
bedrock::{
|
||||
client::{
|
||||
network_settings::CNetworkSettings, play_status::CPlayStatus,
|
||||
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
||||
start_game::Experiments,
|
||||
},
|
||||
frame_set::FrameSet,
|
||||
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::{
|
||||
network_settings::CNetworkSettings, play_status::CPlayStatus,
|
||||
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
||||
start_game::Experiments,
|
||||
},
|
||||
codec::var_uint::VarUInt,
|
||||
frame_set::FrameSet,
|
||||
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
||||
};
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::{resource_pack_stack::ResourcePackStackEntry, resource_packs_info::ResourcePackEntry},
|
||||
server::{login::ClientData, resource_pack_response::SResourcePackResponse},
|
||||
};
|
||||
use pumpkin_util::jwt::AuthError;
|
||||
use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
|
||||
@@ -183,11 +181,37 @@ impl BedrockClient {
|
||||
|
||||
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
|
||||
.await;
|
||||
self.write_game_packet_to_set(
|
||||
&CResourcePacksInfo::new(false, false, false, false, Uuid::default(), String::new()),
|
||||
&mut frame_set,
|
||||
)
|
||||
.await;
|
||||
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
if br_config.enabled {
|
||||
for pack in &br_config.packs {
|
||||
entries.push(ResourcePackEntry {
|
||||
uuid: pack.uuid.to_string(),
|
||||
version: pack.version.clone(),
|
||||
size: pack.size,
|
||||
download_url: pack.download_url.clone(),
|
||||
content_key: pack.content_key.clone(),
|
||||
sub_pack_name: pack.sub_pack_name.clone(),
|
||||
content_id: pack.content_id.clone(),
|
||||
has_scripts: pack.has_scripts,
|
||||
addon_pack: pack.addon_pack,
|
||||
rtx_enabled: pack.rtx_enabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let packs_info = CResourcePacksInfo {
|
||||
resource_pack_required: br_config.force,
|
||||
has_addon_packs: false,
|
||||
has_scripts: false,
|
||||
is_vibrant_visuals_force_disabled: false,
|
||||
world_template_id: uuid::Uuid::nil(),
|
||||
world_template_version: String::new(),
|
||||
resource_packs: entries,
|
||||
};
|
||||
self.write_game_packet_to_set(&packs_info, &mut frame_set)
|
||||
.await;
|
||||
|
||||
self.send_frame_set(frame_set, 0x84).await;
|
||||
|
||||
@@ -234,10 +258,27 @@ impl BedrockClient {
|
||||
debug!("Bedrock: SResourcePackResponse::STATUS_HAVE_ALL_PACKS");
|
||||
let mut frame_set = FrameSet::default();
|
||||
|
||||
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||
|
||||
// Convert your config packs into protocol stack entries
|
||||
let resource_packs = if br_config.enabled {
|
||||
br_config
|
||||
.packs
|
||||
.iter()
|
||||
.map(|pack| ResourcePackStackEntry {
|
||||
uuid: pack.uuid.to_string(),
|
||||
version: pack.version.clone(),
|
||||
sub_pack_name: String::new(),
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
self.write_game_packet_to_set(
|
||||
&CResourcePackStackPacket::new(
|
||||
false,
|
||||
VarUInt(0),
|
||||
br_config.force,
|
||||
resource_packs,
|
||||
CURRENT_BEDROCK_MC_VERSION.to_string(),
|
||||
Experiments {
|
||||
names_size: 0,
|
||||
@@ -248,6 +289,7 @@ impl BedrockClient {
|
||||
&mut frame_set,
|
||||
)
|
||||
.await;
|
||||
|
||||
self.send_frame_set(frame_set, 0x84).await;
|
||||
}
|
||||
SResourcePackResponse::STATUS_COMPLETED => {
|
||||
|
||||
@@ -76,7 +76,7 @@ impl JavaClient {
|
||||
server: &Server,
|
||||
packet: SConfigResourcePack,
|
||||
) {
|
||||
let resource_config = &server.advanced_config.resource_pack;
|
||||
let resource_config = &server.advanced_config.resource_pack.java;
|
||||
if resource_config.enabled {
|
||||
let expected_uuid =
|
||||
uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
||||
|
||||
@@ -368,7 +368,7 @@ impl JavaClient {
|
||||
self.send_packet_now(&CConfigServerLinks::new(&links)).await;
|
||||
}
|
||||
|
||||
let resource_config = &server.advanced_config.resource_pack;
|
||||
let resource_config = &server.advanced_config.resource_pack.java;
|
||||
if resource_config.enabled {
|
||||
let uuid = Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
||||
let resource_pack = CConfigAddResourcePack::new(
|
||||
|
||||
@@ -80,7 +80,7 @@ use pumpkin_protocol::{
|
||||
creative_content::{CreativeContent, Group},
|
||||
gamerules_changed::GameRules,
|
||||
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
||||
update_artributes::{Attribute, CUpdateAttributes},
|
||||
update_attributes::{Attribute, CUpdateAttributes},
|
||||
},
|
||||
network_item::NetworkItemDescriptor,
|
||||
server::text::SText,
|
||||
|
||||
Reference in New Issue
Block a user