commonlibsse_ng_derive_internal/lib.rs
1//! `commonlibsse_ng` macro to automatically generate commonly used code patterns for crate use.
2//!
3
4use proc_macro::TokenStream;
5
6/// Relocates an address using Skyrim runtime-specific relocation IDs.
7///
8/// The `#[relocate]` attribute macro enables dynamic resolution of a pointer using relocation
9/// IDs for Skyrim Special Edition (SE), Anniversary Edition (AE), and optionally VR. It injects code
10/// to resolve the address at runtime and execute user-provided logic through a closure, passing the
11/// resolved pointer as an argument.
12///
13/// # Attributes
14///
15/// | Attribute | Type | Required | Description |
16/// |----------------|-----------|----------|----------------------------------------------------------------------------------|
17/// | `cast_as` | `&str` | Yes | The type to cast the resolved pointer to (e.g., `"*mut bool"`, `"*mut T"`). |
18/// | `default` | `&str` | Yes | The fallback value returned if resolution fails (e.g., `"false"`, `None`). |
19/// | `deref_once` | `bool` | No | If specified, the casted pointer will be dereferenced once(by `read_unaligned`). |
20/// | `id.se` | `u64` | Yes | Relocation ID for Skyrim Special Edition. |
21/// | `id.ae` | `u64` | Yes | Relocation ID for Skyrim Anniversary Edition. |
22/// | `id.vr` | `u64` | No | Relocation ID for Skyrim VR. Defaults to `se` if omitted. |
23///
24/// If `deref_once` is specified and the `cast_as` type is a multi-level pointer (e.g., `*mut *mut T`),
25/// the macro will automatically strip one level and define a helper type alias `DerefType`:
26///
27/// ```rust
28/// type DerefType = *mut T;
29/// ```
30///
31/// This type alias can then be used as the parameter type in the closure.
32///
33/// # Function Body
34///
35/// The function body must be a single closure of the form:
36///
37/// ```rust
38/// |as_type: AsType| { ... }
39/// ```
40///
41/// where `AsType` is either the raw casted pointer or the dereferenced value, depending on `deref_once`.
42///
43/// If resolution fails, the `default` value will be returned instead (parsed as a Rust expression).
44///
45/// # Examples
46///
47/// ## Without deref_once
48/// ```rust
49/// #[commonlibsse_ng_derive_internal::relocate(
50/// cast_as = "*mut EntryPoint",
51/// default = "None",
52/// id(se = 675707, ae = 368994)
53/// )]
54/// #[inline]
55/// fn entry_points(entry_point: ENTRY_POINT) -> Option<NonNull<EntryPoint>> {
56/// |as_type| unsafe { NonNull::new(as_type.add(entry_point as usize)) }
57/// }
58/// ```
59///
60/// ## With deref_once and pointed pointer(e.g. `GetSingleton`)
61/// ```rust
62/// #[commonlibsse_ng_derive_internal::relocate(
63/// cast_as = "*mut *mut INIPrefSettingCollection",
64/// default = "None",
65/// deref_once,
66/// id(se = 524557, ae = 411155)
67/// )]
68/// pub fn get_singleton() -> Option<&'static INIPrefSettingCollection> {
69/// |deref_type: DerefType| unsafe { deref_type.as_ref() }
70/// }
71/// ```
72///
73/// # Notes
74///
75/// - `cast_as` must be a valid Rust type (pointer types encouraged for safety).
76/// - `deref_once` is especially useful for singletons and global pointers.
77/// - This pattern avoids boilerplate and enables declarative relocation definitions.
78///
79/// # See Also
80///
81/// - [`#[relocate_fn]`](relocate_fn) — relocate and invoke a function with arguments instead of reading a pointer.
82#[proc_macro_attribute]
83pub fn relocate(attrs: TokenStream, item: TokenStream) -> TokenStream {
84 let item_fn = syn::parse_macro_input!(item as syn::ItemFn);
85 let crate_root_name = quote::quote! { crate };
86
87 commonlibsse_ng_proc_macro_common::relocate::gen_relocate(
88 attrs.into(),
89 item_fn,
90 crate_root_name,
91 )
92 .into()
93}
94
95/// `relocate_fn` is a procedural macro used to generate a relocated function in Rust.
96/// It allows you to specify function relocation IDs (`se_id`, `ae_id`, and `vr_id`) to relocate a function at runtime.
97/// This macro generates code that attempts to resolve a function's address based on the provided IDs,
98/// and calls the relocated function if the address is valid.
99///
100/// # Attributes
101/// The macro accepts the following arguments:
102///
103/// | Attribute | Description |
104/// |-----------|----------------------------------------------------|
105/// | `se_id` | The ID of the Skyrim Special Edition (mandatory) |
106/// | `ae_id` | The ID of the Skyrim Anniversary Edition (mandatory) |
107/// | `vr_id` | The ID of the Skyrim VR (optional, defaults to `se_id` if not provided) |
108///
109/// The macro generates code that will dynamically resolve the address of the function using these IDs.
110/// If the resolution is successful and the address is valid (non-null and aligned),
111/// the relocated function will be called with the same arguments as the original function.
112/// Otherwise, an error will be logged(`tracing` feature is required), and the program will panic.
113///
114/// # Example
115/// ```rust:no_compile
116/// #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 1, ae_id = 2, vr_id = 3)]
117/// fn my_function(arg1: usize, arg2: usize) -> bool {
118/// tracing::info("arg1 = {arg1}, arg2 = {arg2}"); // We can sandwich the process before that.
119///
120/// // macro is expanded from here.
121/// // 1. id to address
122/// // 2. Execute function with the argument as it is.
123/// }
124/// ```
125///
126/// This will generate a function that attempts to resolve its address using the provided IDs and
127/// call it safely with `arg1` and `arg2` passed as arguments.
128///
129/// # Notes
130/// - The relocation IDs are used for dynamically finding the target function address.
131/// - If no `vr_id` is provided, the `se_id` is used as the default value.
132/// - `SelfSignature`: Function signature for self. like C++ `decltype(T)`
133///
134/// # Panics
135/// - The macro generates code that checks the validity of the resolved address, logging an error and panicking if invalid.
136#[proc_macro_attribute]
137pub fn relocate_fn(attrs: TokenStream, item: TokenStream) -> TokenStream {
138 let item_fn = syn::parse_macro_input!(item as syn::ItemFn);
139 let crate_root_name = quote::quote! { crate };
140
141 commonlibsse_ng_proc_macro_common::relocate_fn::gen_relocate_fn(
142 attrs.into(),
143 item_fn,
144 crate_root_name,
145 )
146 .into()
147}
148
149/// An attribute macro to generate FFI-compatible bitflags from an `enum`.
150///
151/// # Attributes
152/// The macro accepts the following arguments:
153///
154/// | Attribute | Description |
155/// |-------------|--------------------------------------------------------------------------------------------|
156/// | `flag_name` | The Identifier of Flag struct(optional, defaults to `_CEnum` suffix struct if not provided) |
157///
158/// # Why this is necessary
159/// In the context of FFI (Foreign Function Interface), the `enum` type is often represented as `i32` or `u32`.
160/// Using Rust's `enum` in a struct for FFI may cause **undefined behavior** if an invalid or unknown value is received.
161///
162/// This macro prevents it:
163/// - Represent the numeric value of the `enum` in a **Flag** structure that safely represents the value of FFI.
164/// - Provide a `to_enum()` method to return the flags to the `enum`,
165/// returning `None` for invalid values instead of causing undefined behavior.
166/// - Ensure safe and predictable FFI interactions.
167///
168/// # Example
169///
170/// - [Expanded sample](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=037f7efdd562a28e7af4cbb59406602b)
171///
172/// ```rust
173/// #[commonlibsse_ng_derive_internal::ffi_enum] // auto generate `struct MyEnum_CEnum(i32)`
174/// #[repr(i32)]
175/// enum MyEnum {
176/// A = 1,
177/// B = 2,
178/// C = 4,
179/// }
180///
181/// assert_eq!(MyEnum_CEnum::count(), 3);
182///
183/// // FFI -> Enum
184/// let valid = MyEnum_CEnum::A;
185/// assert_eq!(core::mem::size_of::<MyEnum_CEnum>(), core::mem::size_of::<i32>());
186/// assert_eq!(valid.to_enum(), Some(MyEnum::A));
187///
188/// // Invalid flag: using a bit value that is not defined in the enum
189/// let invalid = MyEnum_CEnum(999);
190/// assert_eq!(invalid.to_enum(), None);
191///
192/// // Enum -> FFI
193/// let flag = MyEnum_CEnum::from_enum(MyEnum::B);
194/// assert_eq!(flag, MyEnum_CEnum::B);
195/// ```
196#[proc_macro_attribute]
197pub fn ffi_enum(attrs: TokenStream, item: TokenStream) -> TokenStream {
198 let item_enum = syn::parse_macro_input!(item as syn::ItemEnum);
199 commonlibsse_ng_proc_macro_common::ffi_enum::ffi_enum(attrs.into(), item_enum).into()
200}
201
202/// Converts a regular `enum` into a `bitflags`-compatible type using its variant values.
203///
204/// The `#[to_bitflags]` attribute macro transforms a plain Rust `enum` definition into a [`bitflags`] struct,
205/// while keeping the original enum syntax intact. This is particularly useful when you want to use bitflags
206/// functionality without switching to a `bitflags!` macro or changing enum semantics.
207///
208/// # Syntax
209///
210/// ```rust
211/// #[commonlibsse_ng_derive_internal::to_bitflags]
212/// #[derive(Default)]
213/// pub enum MyFlags {
214/// A = 0b0001,
215/// B = 0b0010,
216/// C = 0b0100,
217///
218/// #[default] // Optional, sets the default bitflags (requires `#[derive(Default)]`)
219/// None = 0,
220/// }
221/// ```
222///
223/// Optionally, you can specify the output type name using the `fn_name` parameter:
224///
225/// ```rust
226/// #[commonlibsse_ng_derive_internal::to_bitflags(fn_name = "MyFlagsBits")]
227/// pub enum MyFlags {
228/// A = 1,
229/// B = 2,
230/// }
231/// ```
232///
233/// # Attributes
234///
235/// | Attribute | Type | Required | Description |
236/// |--------------|-----------|----------|--------------------------------------------------------------------------------|
237/// | `fn_name` | `&str` | No | The name of the generated `bitflags` struct. Defaults to the enum name. |
238///
239/// # Features
240///
241/// - Automatically implements `bitflags!` for the enum using its variant values.
242/// - Honors `#[default]` on a variant if `#[derive(Default)]` is also used.
243/// - Avoids the need to rewrite your enum as a `bitflags!` block.
244/// - Works with enums using `explicit discriminant values`.
245///
246/// # Example
247///
248/// ```rust
249/// #[commonlibsse_ng_derive_internal::to_bitflags]
250/// #[derive(Default)]
251/// pub enum RenderFlags {
252/// Alpha = 0b0001,
253/// Depth = 0b0010,
254/// Stencil = 0b0100,
255///
256/// #[default]
257/// None = 0,
258/// }
259///
260/// let flags = RenderFlags::ALPHA | RenderFlags::DEPTH;
261/// assert!(flags.contains(RenderFlags::ALPHA));
262/// ```
263///
264/// # Notes
265///
266/// - The macro expects all enum variants to have constant discriminant values (e.g., integers or const exprs).
267/// - The original `enum` is preserved in the output (i.e., not removed or renamed).
268///
269/// # Dependencies
270///
271/// - `#[derive(Default)]` and `#[default]` can be used to control the default value of the generated flag struct.
272///
273/// [`bitflags`]: https://docs.rs/bitflags
274#[proc_macro_attribute]
275pub fn to_bitflags(attrs: TokenStream, item: TokenStream) -> TokenStream {
276 let item_enum = syn::parse_macro_input!(item as syn::ItemEnum);
277 let crate_root_name = quote::quote! { crate };
278 commonlibsse_ng_proc_macro_common::to_bitflags::to_bitflags(
279 attrs.into(),
280 item_enum,
281 crate_root_name,
282 )
283 .into()
284}