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 {\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 {\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::>();\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), } }