Pumpkin/pumpkin-codegen/src/wit/packet_mapping.rs
2026-05-16 09:56:18 +02:00

229 lines
9.8 KiB
Rust

use heck::{ToPascalCase, ToSnakeCase};
use std::{fs, path::Path};
use syn::{Fields, Item};
pub fn build_java_mapping() -> String {
let mut output = String::new();
output.push_str("// Generated by pumpkin-codegen. Do not edit.\n");
output.push_str("#![allow(clippy::all)]\n");
output.push_str("#![allow(clippy::pedantic)]\n");
output.push_str("#![allow(unused_imports)]\n");
output.push_str("#![allow(unused_variables)]\n");
output.push_str("use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::java_packets::ClientboundPacket;\n");
output.push_str("use pumpkin_protocol::codec::var_int::VarInt;\n");
output.push_str("use pumpkin_util::version::JavaMinecraftVersion;\n");
output.push_str("use bytes::Bytes;\n\n");
output.push_str("#[must_use]\n");
output.push_str("pub fn serialize_java_packet(packet: &ClientboundPacket, version: JavaMinecraftVersion) -> Option<Bytes> {\n");
output.push_str(" match packet {\n");
process_packets(
"../pumpkin-protocol/src/java/client/play",
&mut output,
"java_packet",
"ClientboundPacket",
"pumpkin_protocol::java::client::play",
false,
);
output.push_str(" _ => None,\n");
output.push_str(" }\n");
output.push_str("}\n");
output
}
pub fn build_bedrock_mapping() -> String {
let mut output = String::new();
output.push_str("\nuse crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::bedrock_packets::ClientboundPacket as BClientboundPacket;\n\n");
output.push_str("#[must_use]\n");
output.push_str(
"pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {\n",
);
output.push_str(" match packet {\n");
process_packets(
"../pumpkin-protocol/src/bedrock/client",
&mut output,
"packet",
"BClientboundPacket",
"pumpkin_protocol::bedrock::client",
true,
);
output.push_str(" _ => None,\n");
output.push_str(" }\n");
output.push_str("}\n");
output
}
fn process_packets(
dir: &str,
output: &mut String,
attr_name: &str,
variant_prefix: &str,
rust_path_prefix: &str,
skip_raknet: bool,
) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths
.map(|e| e.expect("Failed to read entry").path())
.collect();
sorted_paths.sort();
for path in sorted_paths {
if path.is_dir() {
if skip_raknet && path.file_name().unwrap() == "raknet" {
continue;
}
process_packets(
path.to_str().unwrap(),
output,
attr_name,
variant_prefix,
rust_path_prefix,
skip_raknet,
);
continue;
}
if path.extension().is_some_and(|ext| ext == "rs")
&& path.file_name().is_some_and(|name| name != "mod.rs")
{
parse_packet_file(&path, output, attr_name, variant_prefix, rust_path_prefix);
}
}
}
fn parse_packet_file(
path: &Path,
output: &mut String,
attr_name: &str,
variant_prefix: &str,
rust_path_prefix: &str,
) {
let content = fs::read_to_string(path).expect("Failed to read file");
let file = syn::parse_file(&content).expect("Failed to parse file");
for item in file.items {
if let Item::Struct(s) = item {
if !s.attrs.iter().any(|attr| attr.path().is_ident(attr_name)) {
continue;
}
let struct_name = s.ident.to_string();
if struct_name == "CHandshake" {
continue;
} // Skip CHandshake (RakNet)
let wit_case = struct_name.to_pascal_case();
let mut prep_code = String::new();
let mut field_inits = String::new();
let mut possible = true;
if let Fields::Named(fields) = s.fields {
for field in fields.named {
let mut field_name = field.ident.as_ref().unwrap().to_string();
let wit_field = field_name.to_snake_case();
let (type_ident, is_ref, is_slice) = get_type_info(&field.ty);
if field_name == "type" {
field_name = "r#type".to_string();
}
match type_ident.as_str() {
"String" | "str" => {
if is_ref {
field_inits.push_str(&format!(" {}: &data.{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: data.{}.clone(),\n", field_name, wit_field));
}
},
"VarInt" => {
// WIT maps s32 to i32, so try_into is needed if WIT field is i32 and VarInt(i32)
// Actually data.wit_field is i32.
field_inits.push_str(&format!(" {}: VarInt(data.{}),\n", field_name, wit_field));
},
"VarUInt" => {
field_inits.push_str(&format!(" {}: pumpkin_protocol::codec::var_uint::VarUInt(data.{}.try_into().unwrap()),\n", field_name, wit_field));
},
"TextComponent" => {
prep_code.push_str(&format!(" let component_{} = pumpkin_util::text::TextComponent::text(data.{}.clone());\n", wit_field, wit_field));
if is_ref {
field_inits.push_str(&format!(" {}: &component_{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: component_{},\n", field_name, wit_field));
}
},
"BlockPos" => field_inits.push_str(&format!(" {}: pumpkin_util::math::position::BlockPos::new(data.{}.0, data.{}.1, data.{}.2),\n", field_name, wit_field, wit_field, wit_field)),
"Vector3" => {
field_inits.push_str(&format!(" {}: pumpkin_util::math::vector3::Vector3::new(data.{}.0 as _, data.{}.1 as _, data.{}.2 as _),\n", field_name, wit_field, wit_field, wit_field));
},
"Uuid" => {
if is_slice {
prep_code.push_str(&format!(" let vec_{} = data.{}.iter().map(|u| uuid::Uuid::from_u64_pair(u.high, u.low)).collect::<Vec<_>>();\n", wit_field, wit_field));
if is_ref {
field_inits.push_str(&format!(" {}: &vec_{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: vec_{},\n", field_name, wit_field));
}
} else {
prep_code.push_str(&format!(" let uuid_{} = uuid::Uuid::from_u64_pair(data.{}.high, data.{}.low);\n", wit_field, wit_field, wit_field));
if is_ref {
field_inits.push_str(&format!(" {}: &uuid_{},\n", field_name, wit_field));
} else {
field_inits.push_str(&format!(" {}: uuid_{},\n", field_name, wit_field));
}
}
},
"i32" | "u32" | "i64" | "u64" | "bool" | "f32" | "f64" | "u8" | "i8" | "u16" | "i16" => {
field_inits.push_str(&format!(" {}: data.{}.try_into().unwrap(),\n", field_name, wit_field));
},
_ => {
possible = false;
break;
}
}
}
} else {
possible = false;
}
if possible {
output.push_str(&format!(
" {}::{}(data) => {{\n",
variant_prefix, wit_case
));
output.push_str(&prep_code);
output.push_str(&format!(
" let p = {}::{} {{\n",
rust_path_prefix, struct_name
));
output.push_str(&field_inits);
output.push_str(" };\n");
output.push_str(" let mut buf = Vec::new();\n");
if attr_name == "java_packet" {
output.push_str(" crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();\n");
} else {
output.push_str(" crate::net::bedrock::BedrockClient::write_raw_packet(&p, &mut buf).unwrap();\n");
}
output.push_str(" Some(buf.into())\n");
output.push_str(" }\n");
}
}
}
}
fn get_type_info(ty: &syn::Type) -> (String, bool, bool) {
match ty {
syn::Type::Path(tp) => (tp.path.segments.last().unwrap().ident.to_string(), false, false),
syn::Type::Reference(tr) => {
let (name, _, is_slice) = get_type_info(&tr.elem);
(name, true, is_slice)
}
syn::Type::Slice(ts) => {
let (name, _, _) = get_type_info(&ts.elem);
(name, false, true)
}
_ => (String::new(), false, false),
}
}