use std::{
collections::HashSet,
fs::{self, File},
io::{BufRead, BufReader},
path::Path,
};
use quote::{quote, ToTokens};
use syn::{Field, GenericArgument, Item, PathArguments, Type, TypePath};
fn main() {
let proto_file = "nap.proto";
if Path::new(&proto_file).exists() {
println!("cargo:rerun-if-changed={proto_file}");
let _ = fs::create_dir("out/");
prost_build::Config::new()
.out_dir("out/")
.type_attribute(".", "#[derive(trigger_protobuf_derive::CmdID)]")
.type_attribute(".", "#[derive(trigger_protobuf_derive::XorFields)]")
.compile_protos(&[proto_file], &["."])
.unwrap();
let pb_out = Path::new("out/_.rs");
remove_obfuscated_messages(pb_out, Path::new("nap.proto")).unwrap();
apply_message_attributes(pb_out).unwrap();
impl_struct_conversions(
pb_out,
Path::new("../trigger-protocol/src/lib.rs"),
Path::new("out/protocol_map.rs"),
)
.unwrap();
}
}
fn impl_struct_conversions(
protobuf_codegen_path: &Path,
protocol_structures_path: &Path,
output_path: &Path,
) -> std::io::Result<()> {
let pb_gen = syn::parse_file(&fs::read_to_string(protobuf_codegen_path)?).unwrap();
let custom_structs = syn::parse_file(&fs::read_to_string(protocol_structures_path)?).unwrap();
let mut from_impls = proc_macro2::TokenStream::new();
let mut pb_to_cp_unit_match_arms = proc_macro2::TokenStream::new();
let mut cp_unit_to_pb_match_arms = proc_macro2::TokenStream::new();
for item in pb_gen.items.iter() {
let Item::Struct(pb) = item else {
continue;
};
let ident = &pb.ident;
// if it has cmdid, then we want a PB <-> Common Protocol Unit conversion
if pb.attrs.iter().any(|attr| {
attr.meta
.path()
.get_ident()
.map(|i| i == "cmdid")
.unwrap_or(false)
// Should be also defined in list of custom Common Protocol structures
}) && custom_structs.items.iter().any(|st| {
if let Item::Struct(cst) = st {
&cst.ident == ident
} else {
false
}
}) {
pb_to_cp_unit_match_arms.extend(quote! {
#ident::CMD_ID => {
let mut pb_message = #ident::decode(pb)?;
pb_message.xor_fields();
let common_protocol_message = ::trigger_protocol::#ident::from(pb_message);
Ok(Some(common_protocol_message.into()))
}
});
cp_unit_to_pb_match_arms.extend(quote! {
::trigger_protocol::#ident::CMD_ID => {
let common_protocol_message = ::trigger_protocol::#ident::decode(&mut ::std::io::Cursor::new(&unit.blob))?;
let mut pb_message = #ident::from(common_protocol_message);
pb_message.xor_fields();
Ok(Some((#ident::CMD_ID, pb_message.encode_to_vec())))
}
});
}
let Some(Item::Struct(c_struct)) = custom_structs.items.iter().find(|i| {
if let Item::Struct(s) = i {
&s.ident == ident
} else {
false
}
}) else {
continue;
};
let mut assignments = proc_macro2::TokenStream::new();
pb
.fields
.iter()
.filter(|pb_field| c_struct.fields.iter().any(|c_field| c_field.ident == pb_field.ident))
.map(|f| (f, f.ident.as_ref().unwrap()))
.for_each(|(f, ident)| {
if field_is_optional(f) {
assignments.extend(quote! {
#ident: value.#ident.map(|v| v.into()),
});
}
else if field_is_vector(f) {
assignments.extend(quote! {
#ident: value.#ident.into_iter().map(|v| v.into()).collect(),
});
}
else if field_is_hash_map(f) {
assignments.extend(quote! {
#ident: value.#ident.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
});
}
else {
assignments.extend(quote! {
#ident: value.#ident.into(),
});
}
});
from_impls.extend(quote! {
#[allow(unused)]
impl From<#ident> for ::trigger_protocol::#ident {
fn from(value: #ident) -> Self {
Self {
#assignments
..Default::default()
}
}
}
#[allow(unused)]
impl From<::trigger_protocol::#ident> for #ident {
fn from(value: ::trigger_protocol::#ident) -> Self {
Self {
#assignments
..Default::default()
}
}
}
});
}
let generated_code = quote! {
pub fn pb_to_common_protocol_unit(pb_cmd_id: u16, pb: &[u8]) -> Result