1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::parse::{Parse, ParseStream};
5use syn::punctuated::Punctuated;
6use syn::{Expr, ExprLit, ItemImpl, Lit, LitCStr, Token, parse_macro_input};
7
8const PLUGIN_MOD: i32 = 0x0001;
9const PLUGIN_UNL: i32 = 0x0008;
10const PLUGIN_FIX: i32 = 0x0080;
11const PLUGIN_MULTI: i32 = 0x0100;
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14enum PluginKind {
15 #[default]
16 Default,
17 Resident,
18 Oneshot,
19}
20
21struct PluginArgs {
22 name: String,
23 comment: Option<String>,
24 help: Option<String>,
25 hotkey: Option<String>,
26 version: Option<i32>,
27 kind: PluginKind,
28}
29
30impl Parse for PluginArgs {
31 fn parse(input: ParseStream) -> syn::Result<Self> {
32 let mut name = None;
33 let mut comment = None;
34 let mut help = None;
35 let mut hotkey = None;
36 let mut version = None;
37 let mut kind = PluginKind::Default;
38
39 let pairs = Punctuated::<syn::MetaNameValue, Token![,]>::parse_terminated(input)?;
40 for pair in pairs {
41 let Some(key) = pair.path.get_ident() else {
42 return Err(syn::Error::new_spanned(pair.path, "expected identifier"));
43 };
44
45 if key == "name" {
46 if let Expr::Lit(ExprLit {
47 lit: Lit::Str(s), ..
48 }) = pair.value
49 {
50 name = Some(s.value());
51 continue;
52 } else {
53 return Err(syn::Error::new_spanned(
54 pair.value,
55 "expected string literal",
56 ));
57 }
58 }
59
60 if key == "comment" {
61 if let Expr::Lit(ExprLit {
62 lit: Lit::Str(s), ..
63 }) = pair.value
64 {
65 comment = Some(s.value());
66 continue;
67 } else {
68 return Err(syn::Error::new_spanned(
69 pair.value,
70 "expected string literal",
71 ));
72 }
73 }
74
75 if key == "help" {
76 if let Expr::Lit(ExprLit {
77 lit: Lit::Str(s), ..
78 }) = pair.value
79 {
80 help = Some(s.value());
81 continue;
82 } else {
83 return Err(syn::Error::new_spanned(
84 pair.value,
85 "expected string literal",
86 ));
87 }
88 }
89
90 if key == "hotkey" {
91 if let Expr::Lit(ExprLit {
92 lit: Lit::Str(s), ..
93 }) = pair.value
94 {
95 hotkey = Some(s.value());
96 continue;
97 } else {
98 return Err(syn::Error::new_spanned(
99 pair.value,
100 "expected string literal",
101 ));
102 }
103 }
104
105 if key == "version" {
106 if let Expr::Lit(ExprLit {
107 lit: Lit::Int(i), ..
108 }) = pair.value
109 {
110 version = Some(i.base10_parse()?);
111 continue;
112 } else {
113 return Err(syn::Error::new_spanned(
114 pair.value,
115 "expected integer literal",
116 ));
117 }
118 }
119
120 if key == "kind" {
121 if let Expr::Path(ref path) = pair.value
122 && let Some(ident) = path.path.get_ident()
123 {
124 kind = if ident == "default" {
125 PluginKind::Default
126 } else if ident == "resident" {
127 PluginKind::Resident
128 } else if ident == "oneshot" {
129 PluginKind::Oneshot
130 } else {
131 return Err(syn::Error::new_spanned(
132 ident,
133 format!(
134 "unknown kind `{ident}`, expected `default`, `resident`, or `oneshot`"
135 ),
136 ));
137 };
138 continue;
139 } else {
140 return Err(syn::Error::new_spanned(
141 &pair.value,
142 "expected identifier: `default`, `resident`, or `oneshot`",
143 ));
144 }
145 }
146
147 return Err(syn::Error::new_spanned(
148 &pair.path,
149 format!("unknown attribute `{key}`"),
150 ));
151 }
152
153 Ok(Self {
154 name: name.ok_or_else(|| syn::Error::new(input.span(), "missing `name` attribute"))?,
155 comment,
156 help,
157 hotkey,
158 version,
159 kind,
160 })
161 }
162}
163
164fn make_cstr_literal(s: &str) -> LitCStr {
165 let cstring = std::ffi::CString::new(s).expect("string contains null byte");
166 LitCStr::new(&cstring, Span::call_site())
167}
168
169#[proc_macro_attribute]
170pub fn plugin(attr: TokenStream, item: TokenStream) -> TokenStream {
171 let args = parse_macro_input!(attr as PluginArgs);
172 let impl_block = parse_macro_input!(item as ItemImpl);
173 let self_ty = &impl_block.self_ty;
174
175 let name = &args.name;
176 let name_cstr = make_cstr_literal(name);
177 let comment_cstr = make_cstr_literal(args.comment.as_deref().unwrap_or_default());
178 let help_cstr = make_cstr_literal(args.help.as_deref().unwrap_or_default());
179 let hotkey_cstr = make_cstr_literal(args.hotkey.as_deref().unwrap_or_default());
180
181 let base_flags = PLUGIN_MULTI | PLUGIN_MOD;
182 let kind_flag = match args.kind {
183 PluginKind::Default => 0,
184 PluginKind::Resident => PLUGIN_FIX,
185 PluginKind::Oneshot => PLUGIN_UNL,
186 };
187 let computed_flags = base_flags | kind_flag;
188 let version = args.version.unwrap_or(900);
189
190 let expanded = quote! {
191 #impl_block
192
193 extern "C" fn __idalib_plugin_init() -> *mut idalib::ffi::plugin::plugmod_t {
194 let mut idb = match idalib::IDB::current() {
195 Ok(idb) => idb,
196 Err(e) => {
197 let _ = unsafe {
198 idalib::ffi::ida::msg(&format!("[{}] plugin initialisation failed: {e}\n", #name))
199 };
200 return ::std::ptr::null_mut();
201 }
202 };
203
204 let mut ida = idalib::IDA::new(&idb);
205
206 match <#self_ty as idalib::plugin::IDAPlugin>::init(&mut ida, &mut idb) {
207 Ok(plugin) => {
208 let wrapper = idalib::plugin::PlugmodWrapper::new(#name, plugin);
209 let plugmod = Box::new(idalib::ffi::plugin::PlugMod::new(wrapper));
210 unsafe { idalib::ffi::plugin::idalib_create_plugmod(plugmod) }
211 }
212 Err(e) => {
213 ida.msg(&format!("[{}] plugin initialisation failed: {e}\n", #name)).ok();
214 ::std::ptr::null_mut()
215 }
216 }
217 }
218
219 #[unsafe(no_mangle)]
220 pub static mut PLUGIN: idalib::ffi::plugin::plugin_t = idalib::ffi::plugin::plugin_t {
221 version: #version,
222 flags: #computed_flags,
223 init: Some(__idalib_plugin_init),
224 term: None,
225 run: None,
226 comment: #comment_cstr.as_ptr(),
227 help: #help_cstr.as_ptr(),
228 wanted_name: #name_cstr.as_ptr(),
229 wanted_hotkey: #hotkey_cstr.as_ptr(),
230 };
231 };
232
233 expanded.into()
234}