229 lines
9.8 KiB
Rust
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),
|
|
}
|
|
}
|